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