-
[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