설계 원칙은 유지 가능하고(maintainable), 이해 가능하고(understandable), 유연한(flexible) 소프트에어를 만들 수 있게 해준다.
어플리케이션의 크기가 커질수록 복잡성(complexity)을 줄여준다.
한 클래스는 하나의 책임만을 가지고, 변경될 이유는 단 하나여야 한다.
public class Book {
private String name;
private String author;
private String text;
//constructor, getters and setters
// methods that directly relate to the book properties
public String replaceWordInText(String word){
return text.replaceAll(word, text);
}
public boolean isWordInText(String word){
return text.contains(word);
}
}
Book
클래스는 잘 작동하고, 우리는 어플리케이션에서 많은 책들을 저장할 수 있다.text
를 콘솔에 출력하거나 읽을 수 없다면 정보를 저장하는 것은 무엇에 좋을까?public class Book {
//...
void printTextToConsole(){
// our code for formatting and printing the text
}
}
Book
에 print()
메서드를 추가해보았다. 이 코드는 단일 책임 원칙을 위반한다.public class BookPrinter {
// methods for outputting text
void printTextToConsole(String text){
//our code for formatting and printing the text
}
void printTextToAnotherMedium(String text){
// code for writing to any other location..
}
}
Book
이 출력도 해야하는 의무에서 벗어났고, BookPrinter
가 text
를 다른 매체에 전송할 수 있게까지 수정하였다.소프트웨어 구성요소(컴포넌트, 클래스, 모듈, 함수)가 확장에는 열려있고, 변경에는 닫혀있어야 한다.
public class Guitar {
private String make;
private String model;
private int volume;
//Constructors, getters & setters
}
Guitar
가 약간 지루해졌고 flame 패턴을 입혀서 더 힙하게 만들고 싶다.Guitar
클래스에 단순히 flamePattern
을 추가하고자 하는 마음이 들 것이지만, 어플리케이션에 어떤 에러가 발생할지 모른다.public class SuperCoolGuitarWithFlames extends Guitar {
private String flameColor;
//constructor, getters + setters
}
Guitar
클래스를 단순히 확장(extend)하였다.상위 타입은 하위 타입으로 대체해도 항상 문제없이 동작해야 한다.
Rectangle
)은 정사각형(Square
)으로 교체될 수 없다.public class Rectangle {
private int width;
private int height;
// Constructor
public int getWidth() {
return width;
}
public int getHeight() {
return height;
}
public void setWidth(int width) {
this.width = width;
}
public void setHeight(int height) {
this.height = height;
}
}
public class Square extends Rectangle {
/*
@Override
public void setWidth(int length) {
this.width = length;
this.height = length;
}
@Override
public void setHeight(int length) {
this.width = length;
this.height = length;
}
*/
}
Square
가 Rectangle
을 다루는 문맥에서 사용될 경우, 정사각형의 크기는 독립적으로 변경하면 안 되기 때문에(정사각형은 항상 너비와 높이가 같기 때문) 예기치 못한 행동을 하게 된다.Square
의 setter를 수정해서 정사각형의 불변 조건(너비=높이)을 유지하면, 크기를 독립적으로 변경할 수 있다고 설명한 Rectangle
의 할당자의 사후 조건을 무력화(위반)한다.큰 인터페이스를 작고 구체적으로 분리시킴으로써 클래스가 필요한 메서드에만 의존해야 한다.
public interface BearKeeper {
void washTheBear();
void feedTheBear();
void petTheBear();
}
public interface BearCleaner {
void washTheBear();
}
public interface BearFeeder {
void feedTheBear();
}
public interface BearPetter {
void petTheBear();
}
public class BearCarer implements BearCleaner, BearFeeder {
public void washTheBear() {
//I think we missed a spot...
}
public void feedTheBear() {
//Tuna Tuesdays...
}
}
public class CrazyPerson implements BearPetter {
public void petTheBear() {
//Good luck with that!
}
}
BookPrinter
클래스도 인터페이스 분리 원칙을 적용시킬 수 있다. 간단한 print()
메서드만 가진 Printer
인터페이스를 만들고, ConsoleBookPrinter
와 OtherMediaBookPrinter
클래스가 구현하도록 한다.소프트웨어 모듈을 분리(decoupling)하는 특정 형식
public class Windows98Machine {
private final StandardKeyboard keyboard;
private final Monitor monitor;
public Windows98Machine() {
keyboard = new StandardKeyboard();
monitor = new Monitor();
}
}
Windows98Machine
의 생성자에서 new
키워드를 통해 StandardKeyboard
와 Monitor
를 선언하기 때문에, 세 클래스가 강하게 결합하고 있다.Windows98Machine
를 테스트하기 어렵다.StandardKeyboard
를 다른 키보드로 바꾸고 싶을때 변경하기 어렵게 만든다. Monitor
도 마찬가지.public interface Keyboard { }
public class StandardKeyboard implements Keyboard { }
public class Windows98Machine{
private final Keyboard keyboard;
private final Monitor monitor;
public Windows98Machine(Keyboard keyboard, Monitor monitor) {
this.keyboard = keyboard;
this.monitor = monitor;
}
}
Windows98Machine
클래스에 Keyboard
의존성을 추가하는 것이 용이해졌다.StandardKeyboard
클래스를 Keyboard
인터페이스로 수정했다.Keyboard
추상화를 통해 연결되어있다(communicate).StandardKeyboard
가 아닌 Keyboard
인터페이스의 다른 구현체로 변경하거나 Windows98Machine
을 테스트하기 더 쉬워졌다.