ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [Refactoring] 18. 중재자
    클린코드 2022. 9. 22. 19:00
     

    코딩으로 학습하는 리팩토링 - 인프런 | 강의

    리팩토링은 소프트웨어 엔지니어가 갖춰야 할 기본적인 소양 중 하나입니다. 이 강의는 인텔리J와 자바를 사용하여 보다 실용적인 방법으로 다양한 코드의 냄새와 리팩토링 기술을 설명하고 직

    www.inflearn.com

    | 인프런 - 백기선님의 코딩으로 학습하는 리팩토링 강의를 수강하며 정리한 글입니다.


    중재자
    • 캡슐화를 통해 내부의 구체적인 정보를 최대한 감출 수 있으나, 어떤 클래스의 메서드가 다른 클래스의 메소드 호출을 대부분 위임하고 있다면 중재자를 제거하거나 클라이언트가 해당 클래스를 직접 사용하도록 코드를 개선할 수 있다.
    • 캡슐화는 항상 정도가 있다. 과도한 캡슐화는 독이 된다.
    • 관련 리팩토링
      • 중재자 제거하기 -> 리팩토링을 사용해 클라이언트가 필요한 클래스를 직접 사용하도록 개선
      • 함수 인라인 -> 메서드를 호출한 쪽으로 코드를 옮겨 중재자 제거
      • 슈퍼 클래스를 위임으로 바꾸기
      • 서브 클래스를 위임으로 바꾸기

    Refactoring 1. 중재자 제거하기

    • 위임 숨기기의 반대에 해당하는 리팩토링
    • 필요한 캡슐화의 정도는 시간에 따라 그리고 상황에 따라 바뀔 수 있다.
    • 위임하고 있는 객체를 클라이언트가 사용할 수 있도록 Getter 제공, 클라이언트는 메시지 체인을 사용하도록 코드를 고친다.
    • Law of Demeter를 지나치게 따르기 보다는 상황에 맞게 활용하는 것이 좋다.
      • Law of Demeter -> 가장 가까운 객체만 사용한다.
    Before
    public class Department {
    
        private Person manager;
        private String name;
        private int count;
    
        public Department(Person manager, String name, int count) {
            this.manager = manager;
            this.name = name;
            this.count = count;
        }
    
        public Person getManager() {
            return manager;
        }
    
        public String getName() {
            return name;
        }
    
        public int getCount() {
            return count;
        }
    }
    public class Person {
    
        private Department department;
    
        private String name;
    
        public Person(String name, Department department) {
            this.name = name;
            this.department = department;
        }
    
        public Person getManager() {
            return this.department.getManager();
        }
    
        public String getDepartmentName() {
            return this.department.getName();
        }
    
        public int getDepartmentCount() {
            return this.department.getCount();
        }
    }

    => Department의 정보를 지나치게 Person을 거쳐서 가져온다.

    After
    public class Department {
    
        private Person manager;
        private String name;
        private int count;
    
        public Department(Person manager, String name, int count) {
            this.manager = manager;
            this.name = name;
            this.count = count;
        }
    
        public Person getManager() {
            return manager;
        }
    
        public String getName() {
            return name;
        }
    
        public int getCount() {
            return count;
        }
    }
    
    public class Person {
    
        private Department department;
    
        private String name;
    
        public Person(String name, Department department) {
            this.name = name;
            this.department = department;
        }
    
        public Department getDepartment() {
            return department;
        }
    }

    => Department를 반환하고 해당 객체에서 접근하도록, Person이라는 중개자를 제거


    Refactoring 2. 슈퍼 클래스를 위임으로 바꾸기

    • 객체 지향에서 상속은 기존의 기능을 재사용하기에 매우 쉽고 강력한 방법이지만 때로는 적절하지 않을 때가 존재한다.
      • 서브 클래스는 슈퍼 클래스의 모든 기능을 지원해야 한다.
      • 서브클래스는 슈퍼 클래스의 변경에 취약하다.
    • 상속이 적절하지 않다고 판단될 때 사용하는 리팩토링이다.
    Before
    public class CategoryItem {
    
        private Integer id;
    
        private String title;
    
        private List<String> tags;
    
        public CategoryItem(Integer id, String title, List<String> tags) {
            this.id = id;
            this.title = title;
            this.tags = tags;
        }
    
        public Integer getId() {
            return id;
        }
    
        public String getTitle() {
            return title;
        }
    
        public boolean hasTag(String tag) {
            return this.tags.contains(tag);
        }
    }
    
    public class Scroll extends CategoryItem {
    
        private LocalDate dateLastCleaned;
    
        public Scroll(Integer id, String title, List<String> tags, LocalDate dateLastCleaned) {
            super(id, title, tags);
            this.dateLastCleaned = dateLastCleaned;
        }
    
        public long daysSinceLastCleaning(LocalDate targetDate) {
            return this.dateLastCleaned.until(targetDate, ChronoUnit.DAYS);
        }
    }
    After
    public class CategoryItem {
    
        private Integer id;
    
        private String title;
    
        private List<String> tags;
    
        public CategoryItem(Integer id, String title, List<String> tags) {
            this.id = id;
            this.title = title;
            this.tags = tags;
        }
    
        public Integer getId() {
            return id;
        }
    
        public String getTitle() {
            return title;
        }
    
        public boolean hasTag(String tag) {
            return this.tags.contains(tag);
        }
    }
    
    public class Scroll {
    
        private LocalDate dateLastCleaned;
    
        private CategoryItem categoryItem;
    
        public Scroll(Integer id, String title, List<String> tags, LocalDate dateLastCleaned) {
            this.dateLastCleaned = dateLastCleaned;
            this.categoryItem = new CategoryItem(id, title, tags);
        }
    
        public long daysSinceLastCleaning(LocalDate targetDate) {
            return this.dateLastCleaned.until(targetDate, ChronoUnit.DAYS);
        }
    }

    => 불필요한 상속 관계를 위임으로 변경


    Refactoring 3. 서브 클래스를 위임으로 바꾸기

    • 대부분의 프로그래밍 언어에서 상속은 오직 한 번만 사용할 수 있다.
      • A이면서 B가 될 수 있다.
      • 어떤 객체를 두 가지 이상의 카테고리로 분류해야 한다면? -> 위임을 사용하면 얼마든지 여러 다른 객체로 위임할 수 있다.
    • 슈퍼 클래스를 변경할 때 서브 클래스까지 신경써야 한다.
      • 만약 서브 클래스가 다른 모듈에 존재한다면? -> 위임을 사용한다면 중간에 인터페이스를 만들어 의존성을 줄일 수 있다.
    • 반드시 위임으로 바꿀 필요는 없다. -> 상황에 따라 적용
    Before
    public class Booking {
    
        protected Show show;
    
        protected LocalDateTime time;
    
        public Booking(Show show, LocalDateTime time) {
            this.show = show;
            this.time = time;
        }
    
        public boolean hasTalkback() {
            return this.show.hasOwnProperty("talkback") && !this.isPeakDay();
        }
    
        protected boolean isPeakDay() {
            DayOfWeek dayOfWeek = this.time.getDayOfWeek();
            return dayOfWeek == DayOfWeek.SATURDAY || dayOfWeek == DayOfWeek.SUNDAY;
        }
    
        public double basePrice() {
            double result = this.show.getPrice();
            if (this.isPeakDay()) result += Math.round(result * 0.15);
            return result;
        }
    
    }
    
    public class PremiumBooking extends Booking {
    
        private PremiumExtra extra;
    
        public PremiumBooking(Show show, LocalDateTime time, PremiumExtra extra) {
            super(show, time);
            this.extra = extra;
        }
    
        @Override
        public boolean hasTalkback() {
            return this.show.hasOwnProperty("talkback");
        }
    
        @Override
        public double basePrice() {
            return Math.round(super.basePrice() + this.extra.getPremiumFee());
        }
    
        public boolean hasDinner() {
            return this.extra.hasOwnProperty("dinner") && !this.isPeakDay();
        }
    }
    
    public class PremiumExtra {
    
        private List<String> properties;
    
        private double premiumFee;
    
        public PremiumExtra(List<String> properties, double premiumFee) {
            this.properties = properties;
            this.premiumFee = premiumFee;
        }
    
        public double getPremiumFee() {
            return premiumFee;
        }
    
        public boolean hasOwnProperty(String property) {
            return this.properties.contains(property);
        }
    }
    
    public class Show {
    
        private List<String> properties;
    
        private double price;
    
        public Show(List<String> properties, double price) {
            this.properties = properties;
            this.price = price;
        }
    
        public boolean hasOwnProperty(String property) {
            return this.properties.contains(property);
        }
    
        public double getPrice() {
            return price;
        }
    }
    After
    public class Booking {
    
        protected Show show;
    
        protected LocalDateTime time;
    
        protected PremiumDelegate premiumDelegate;
    
        public Booking(Show show, LocalDateTime time) {
            this.show = show;
            this.time = time;
        }
    
        public static Booking createBooking(Show show, LocalDateTime time) {
            return new Booking(show, time);
        }
    
        public static Booking createPremiumBooking(Show show, LocalDateTime time, PremiumExtra extra) {
            Booking booking = createBooking(show, time);
            booking.premiumDelegate = new PremiumDelegate(booking, extra);
            return booking;
        }
    
        public boolean hasTalkback() {
            return this.premiumDelegate != null ? premiumDelegate.hasTalkback():
                    this.show.hasOwnProperty("talkback") && !this.isPeakDay();
        }
    
        protected boolean isPeakDay() {
            DayOfWeek dayOfWeek = this.time.getDayOfWeek();
            return dayOfWeek == DayOfWeek.SATURDAY || dayOfWeek == DayOfWeek.SUNDAY;
        }
    
        public double basePrice() {
            double result = this.show.getPrice();
            if (this.isPeakDay()) result += Math.round(result * 0.15);
            return this.premiumDelegate != null ? premiumDelegate.extendPrice(result) : result;
        }
    
        public boolean hasDinner() {
            return premiumDelegate != null ? premiumDelegate.hasDinner() : false;
        }
    
    }
    
    public class PremiumDelegate {
    
        private Booking host;
        private PremiumExtra extra;
    
        public PremiumDelegate(Booking host, PremiumExtra extra) {
            this.host = host;
            this.extra = extra;
        }
    
        public boolean hasTalkback() {
            return this.host.show.hasOwnProperty("talkback");
        }
    
        public double extendPrice(double result) {
            return Math.round(result + this.extra.getPremiumFee());
        }
    
        public boolean hasDinner() {
            return this.extra.hasOwnProperty("dinner") && !this.host.isPeakDay();
        }
    }
    
    public class PremiumExtra {
    
        private List<String> properties;
    
        private double premiumFee;
    
        public PremiumExtra(List<String> properties, double premiumFee) {
            this.properties = properties;
            this.premiumFee = premiumFee;
        }
    
        public double getPremiumFee() {
            return premiumFee;
        }
    
        public boolean hasOwnProperty(String property) {
            return this.properties.contains(property);
        }
    }
    
    public class Show {
    
        private List<String> properties;
    
        private double price;
    
        public Show(List<String> properties, double price) {
            this.properties = properties;
            this.price = price;
        }
    
        public boolean hasOwnProperty(String property) {
            return this.properties.contains(property);
        }
    
        public double getPrice() {
            return price;
        }
    }

    => PremiumDelegate를 만들어 상속을 위임으로 푼다. -> Booking을 상속한 PremiumBooking 제거

    '클린코드' 카테고리의 다른 글

    [Refactoring] 20. 거대한 클래스  (0) 2022.09.24
    [Refactoring] 19. 내부자 거래  (1) 2022.09.23
    [Refactoring] 17. 메시지 체인  (0) 2022.09.21
    [Refactoring] 16. 임시 필드  (1) 2022.09.20
    [Refactoring] 15. 추측성 일반화  (0) 2022.09.18

    댓글

Designed by Tistory.