느리더라도 꾸준히

[OOP]객체 지향 프로그래밍(OOP) - (0)설계 본문

Java

[OOP]객체 지향 프로그래밍(OOP) - (0)설계

테디규 2022. 11. 16. 17:45

목표

본문의 글은 설계 내용대로 코드를 작성한 부분입니다.

본문의 코드들은 객체지향적이지 않게 설계가 되어있습니다. 글 시리즈를 진행하면서 하나하나 객체지향의 특성을 입히고 객체 지향적인 코드로 만들어 보도록 하겠습니다.

본문

본문에 들어가기 전에 전체적인 코드를 어떻게 작성했는지 기록해보겠습니다.

전체 적인 코드 작성법

기본적인 전제

객체 사이 어떤 멤버들이 필요한지 유의하여 필드와 메서드를 선언해야한다.

  • 객체간 포함 관계의 변수들은 생성자를 통해 값을 초기화 시켜준다.
    • 사용자 정의 참조형 클래스 타입의 변수를 필드로 선언하는 관계(HAS A의 관계)
  • 한 클래스 내에서 모든 메서드가 사용하는 변수와 객체들은 필드 위치에 선언하고, 임시로 사용하는 변수는 지역변수로 사용한다.
  • 필드, 메서드의 캡슐화 법칙을 지키려고 노력한다.
    • 필드는 기본적으로 private으로 선언하고 getter, setter 로 수정할수 있도록 한다.
    • 메서드는 실제 로직을 최대한 감추도록 은닉하여 작성한다.

OOP로 변환 후

  1. 변동이 예상되는 경우의 객체 관계는 의존관계가 아니도록 작성한다.
  2. 객체사이의 의존관계 주입은 생성자로 주입한다.
    • 누락으로 인한 런타임 에러를 방지하기 위해 생성자로 주입했다.

전체적인 클래스 의존관계도

앞으로 우리는 할인 로직 실행시 각 고객들이 얼마의 가격으로 핸드폰을 구매할수 있는지 콘솔에 출력해 줍니다.

출력결과

고객이름 : 이직원 , 할인된요금 : 990000
고객이름 : 김무개 , 할인된요금 : 1000000
고객이름 : 최학생 , 할인된요금 : 900000

패키지 구조

패키지는 비슷한 목적을 공유하는 클래스들과 인터페이스를 묶어놓을 수 있도록 생성하였습니다.

ex) 고객 , 할인

PhoneInfo는 사실 상품 패키지로 만들어서 넣을 수 있습니다만, 하나의 상품으로 진행할 예정이므로 위와 같이 진행하겠습니다.

상품 정보 PhoneInfo

public class PhoneInfo {
    private int price;
    private String kind;

    public PhoneInfo(int price, String kind) {
        this.price = price;
        this.kind = kind;
    }

    public int getPrice() {
        return price;
    }

}

필드를 private로 설정하고 getter를 이용해 값을 꺼내오도록 하고 있습니다. 추후 설명할 캡슐화를 적용시키고 있는 코드입니다.

실행 파일 Main

public class Main {
    public static void main(String[] args) {
        MartApp martApp = new MartApp();
        martApp.start();
    }
}

App 파일 MartApp

public class MartApp {
    // 고객정보
    CustomerRepository customerRepository = new CustomerRepository();
    Customer[] customers = customerRepository.findAll();
    int originalPrice = new PhoneInfo(1000000, "iphone").getPrice();

    MartService martService = new MartService(customers, originalPrice);

    public void start(){
        // 할인 로직 시작
        martService.service();
    }

        // 직원관리 로직

        // 유통관리 로직

}

Java에서 기본적으로 실행시켜주는 main 메서드는 static 메서드 입니다. static 메서드에는 인스턴스 멤버값들을 사용할 수가 없기 때문에, 따로 인스턴스 메서드를 가지고 있는 MartApp을 생성해줍니다.

위의 코드만으로는 인스턴스 멤버값들 이라고 뭐가 달라지지 생각하실수도 있습니다.

현재로선 로직히 할인 로직 하나지만, 만약 MartApp 에 여러가지 로직기능이 존재한다면 인스턴스 멤버들을 선언하여 클래스 MartApp 내에서 공유시킬 수 있습니다.(재사용성)

할인 로직 서비스 MartService

public class MartService {
    private Customer[] customers;
    int originalPrice;

    StudentDiscountCondition studentDiscountCondition = new StudentDiscountCondition(new RatePolicy(10));
    EmployeeDiscountCondition employeeDiscountCondition = new EmployeeDiscountCondition(new AmountPolicy(10000));

    public MartService(Customer[] customers, int originalPrice) {
        this.customers = customers;
        this.originalPrice = originalPrice;
    }
        // 고객의 할인된 핸드폰 가격을 저장해줄 Map
    HashMap<Customer, Integer> discountedHashMap = new HashMap<>();

    // 주요 로직 : 고객마다의 할인 요금을 구해서 출력해주자!!
    public void service(){
        // 각 고객마다 할인을 적용한다.
        DiscountAllCustomers();

        // 각 고객의 할인 요금 출력한다.
        printDiscountedPrice();
    }

    private void DiscountAllCustomers() {
        for(Customer customer : customers){
            String customerType = customer.getCustomerType();
            if(customerType == "일반") discountedHashMap.put(customer, originalPrice);
            else if(customerType == "직원") {
                discountedHashMap.put(customer,
                        employeeDiscountCondition.applyDiscountPolicy(originalPrice));
            }else if(customerType == "학생") {
                discountedHashMap.put(customer,
                        studentDiscountCondition.applyDiscountPolicy(originalPrice));
            }else {
                System.out.println("처음보는 유형입니다.");
            }
        }
    }

    private void printDiscountedPrice() {
        System.out.println("할인 요금을 출력합니다.");
        Set<Customer> keySet = discountedHashMap.keySet();
        Iterator<Customer> iter = keySet.iterator();
        while(iter.hasNext()){
            Customer customer = iter.next();
            System.out.printf("고객이름 : %s , 할인된요금 : %d\n", customer.getName(), discountedHashMap.get(customer));
        }
    }
}

실제 고객들의 조건과 핸드폰 가격을 이용해서 할인액을 구해주는 로직을 실행하는 클래스입니다.

  • DiscountAllCustomers() : 각 고객마다 할인을 적용해주는 메서드입니다.
  • printDiscountedPrice() : 각 고객의 할인 요금 출력해주는 메서드입니다.
  • discountedHashMap(); : 고객의 할인된 핸드폰 가격을 저장해줄 저장소입니다. 따로 저장소 클래스를 만들지 않고 컬렉션에 저장하도록 하겠습니다.
  • applyDiscountPolicy() : 해당 할인 조건에 알맞은 할인 정책을 적용시킨 할인 금액을 가져옵니다.

Customer 패키지

Customer

public class Customer implements Comparable<Customer>{
    private int id;
    private String name;
    private String customerType;

    public Customer(int id, String name, String customerType) {
        this.id = id;
        this.name = name;
        this.customerType = customerType;
    }

    public String getCustomerType() {
        return customerType;
    }

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    @Override
    public int compareTo(Customer c) {
        return name.compareTo(c.getName());
    }
}

Employee

public class Employee extends Customer{

    private String martName;

    public Employee(int id, String name, String customerType, String martName) {
        super(id, name, customerType);
        this.martName = martName;
    }

    public String getMartName() {
        return martName;
    }
}

Student

public class Student extends Customer{
    private String schoolName;

    public Student(int id, String name, String customerType, String schoolName) {
        super(id, name, customerType);
        this.schoolName = schoolName;
    }
    public String getSchoolName() {
        return schoolName;
    }
}

모두 다 캡슐화를 적용한 모습을 볼 수 있습니다.

CustomerRepository

public class CustomerRepository {
    // 아래와 같이 총 세명의 고객이 존재한다고 하자.
    private Customer[] customers = new Customer[]{
            new Customer(1, "김무개","일반"),
            new Student(2, "최학생", "학생", "한국고등학교"),
            new Employee(3, "이직원", "직원", "대한마트")
    };

    public Customer[] findAll(){
        return customers;
    }
}

객체들을 생성하고 관리해주는 저장소입니다. 할인 로직이 너무 많은 책임( 로직진행, 객체생성, 객체 관리 등)지지 않기 위해 생성 되었습니다.

  • 객체 지향 법칙 : SRT를 따라서 생성함.

discount 패키지

EmployeeDiscountCondition

public class EmployeeDiscountCondition {
    AmountPolicy amountPolicy;

    public EmployeeDiscountCondition(AmountPolicy amountPolicy) {
        this.amountPolicy = amountPolicy;
    }

    public int applyDiscountPolicy(int price){
        return amountPolicy.discount(price);
    }

}

StudentDiscountCondition

public class StudentDiscountCondition {
    RatePolicy ratePolicy;

    public StudentDiscountCondition(RatePolicy ratePolicy) {
        this.ratePolicy = ratePolicy;
    }

    // 할인 정책을 적용해서 할인값을 리턴하는 메서드
    public int applyDiscountPolicy(int price){
      return ratePolicy.discount(price);
    }

}

할인 조건에 관한 클래스들입니다. 각 고객들은 해당 할인 조건에 해당되면 가격 할인을 받을수 있습니다. 즉 모든 고객들은 학생인지, 직원인지, 어느쪽도 아닌지 검증이 필요합니다.

현재는 직원일 경우, 정액할인을 학생일 경우 정률할인을 적용하였습니다.

AmountPolicy

public class AmountPolicy {
    private int discountAmount;

    public AmountPolicy(int discountAmount) {
        this.discountAmount = discountAmount;
    }

    public int discount(int price){
        return price - discountAmount;
    }
}

RatePolicy

public class RatePolicy {
    private int discountRate;

    public RatePolicy(int discountRate) {
        this.discountRate = discountRate;
    }

    public int discount(int price){
        return price - (price * discountRate / 100);
    }
}

각각 정액할인, 정률 할인 정책을 구현한 코드입니다. 이들은 할인 조건클래스들의 매개변수로 사용됩니다. 즉 각 할인 조건이 정해졌을때 적용할 할인정책을 나타내는 클래스입니다.

모든 클래스 설명이 끝났습니다. 자 이제 유연한 확장과 재사용성을 목적으로 객체 지향 코드로 변환해봅시다!

Comments