1. 캡슐화 되어야 한다.
- 클래스를 개발할 때 기본적으로 구현을 감추고, 외부 객체와 상호작용 하는 부분만 노출한다.
- 외부의 잘못된 사용을 방지한다.
- 경계에서 배웠던 부분(Map)
2. 단일 책임 원칙(SRP)
클래스는 작아야 한다.
- 함수와 마찬가지로 클래스도 작아야 한다.
- 함수는 라인 수로 크기를 측정했는데, 클래스는 맡은 책임의 수로 크기를 측정한다.
- 클래스 설명은 만일(if), 그리고(and), 하며(or), 하지만(but)을 사용하지 않고 25단어 내외로 가능해야 한다. -> 책임이 한가지 이어야 한다.
- 작은 클래스가 많은 시스템이든 큰 클래스가 몇 개 뿐이 시스템이든 돌아가는 푸품은 그 수가 비슷하다.
- 큼직한 다목적 클래스 몇 개로 이뤄진 시스템은 (변경을 가할 때) 당장 알 필요가 없는 사실까지 들이밀어 독자를 방해한다.
- 작은 클래스는 각자 맡은 책임이 하나며, 변경할 이유가 하나며, 다른 작은 클래스와 협력해 시스템에 필요한 동작을 수행한다.
예제 1. 나쁜 예
public class SuperDashboard extends JFrame implements MetaDataUser {
public Component getLastFocusedComponent()
public void setLastFocused(Component lastFocused)
public int getMajorVersionNumber()
public int getMinorVersionNumber()
public int getBuildNumber()
}
- focuse, version 두가지 책임이 보인다.
예제 2. 좋은 예
public Version {
public int getMajorVersionNumber()
public int getMinorVersionNumber()
public int getBuildNumber()
}
3. 낮은 결합도, 높은 응집도
- 결합도란 다른 모듈간의 의존도를 말한다.
- 응집도란 모듈 내부의 기능 집중도를 말한다.
- 결합도는 낮을 수록, 응집도는 높을 수록 유지보수성이 좋다.
1) 문제점
- 결합도가 높은 클래스의 문제점
- 연관된 클래스가 변경되면 수정이 필요하다.
- 결합도가 높으면 연관된 클래스들을 모두 이해해야 한다.
- 응집도가 낮은 클래스의 문제점
- 여러 기능이 있으므로 이해하기 어렵다
- 재사용하기 어렵다.
2) 낮은 결합도
- 시스템의 결합도를 낮추면 유연성과 재사용성도 더욱 높아진다.
- DIP : 클래스가 상세한 구현이 아니라 추상화에 의존해야한다.
- 추상화를 이용하면 테스트코드 짜기에 용이하다.
예제 3. 나쁜 예
public class TokyoStockExchange {
public Money currentPrice(String symbol);
}
public Protfolio {
private TokyoStockExchange tokyoStockExchange;
public Portfolio(TokyoStockExchange exchange) {
this.tokyoStockExchange = tokyoStockExchange;
}
}
- TokyoStockExchange 함수의 API가 5분마다 값이 달라지는 상황이다.
- Portfolio 클래스 테스트 코드를 짜기 어렵다.
예제 4. 좋은 예
public interface StockExchange {
Money currentPrice(String symbol);
}
public class TokyoStockExchange implements StockExchange {
public Money currentPrice(String symbol) {
}
}
public Portfolio {
pricate stockExchange exchange;
public Portfolio(StockExchange exchange) {
this.exchange = exchange;
}
}
- StockExchange 인터페이스를 통해 Portfolio와 tokyoStockExchange의 결합도를 끊어준다.
예제 5. 예제 4의 테스트코드
public class PortfolioTest {
private FixedStockExchangeStub exchange;
private Portfolio portfolio;
@Before
protected void setUp() throws Exception {
exchange = new FixedStockExchangeStub();
exchange.fix("MSFT", 100);
portfolio = new Portfolio(exchange);
}
@Test
public void GivenFiveMSFTTotalShouldBe500() throws Exception {
portfolio.add(5, "MSFT");
AssertEquals(500, portfolio.value());
}
}
- 객체를 Mockking하면 변경되는 클래스도 테스트할 수 있다.
- 확장될 가능성이 적다면 일단 결합하고, 나중에 추상화해도 좋다.
3) 높은 응집도
- 클래스는 인스턴스 변수 수가 적어야 한다.
- 메서드는 인스턴스 변수를 하나 이상 사용해야 한다.
- 메서드가 인스턴스 변수를 많이 사용할수록 응집도가 높다
- 응집도가 높다 = 클래스에 속한 메서드와 변수가 서로 의존하며 논리적인 단위로 묶인다 = 서로 관계있는 애들만 모여있다.
- 클래스가 응집도를 잃어간다면 함수를 쪼개야한다.
예제 6. 좋은 예
public class Stack {
private int topOfStack = 0;
List<Integer> elements = new LinkedList<Integer>();
public int size() {
return topOfStack;
}
public void push(int element) {
topOfStack++;
elements.add(element);
}
public int pop() throws PoppedWhenEmpty {
if (topOfStack == 0)
throw new PoppedWhenEmpty();
int element = elements.get(--topOfStack);
elements.remove(topOfStack);
return element;
}
}
4. 변경하기 쉬워야 한다.
예제 7. 나쁜 예
public class Sql {
public Sql(String table, Column[] columns)
public String create()
public String insert(Object[] fields)
public String selectAll()
public String findByKey(String keyColumn, String keyValue)
public String select(Column column, String pattern)
public String select(Criteria criteria)
public String preparedInsert()
private String columnList(Column[] columns)
private String valuesList(Object[] fields, final Column[] columns)
private String selectWithCriteria(String criteria)
private String placeholderList(Column[] columns)
}
- 새로운 SQL을 추가할 때도 수정이 발생하고, 기존 SQl문을 수정할 때도 수정이 발생하므로 OCP가 위반된다.
예제 8. 좋은 예
abstract public class Sql {
public Sql(String table, Column[] columns)
abstract public String generate();
}
public class CreateSql extends Sql {
public CreateSql(String table, Column[] columns)
@Override public String generate()
}
public class SelectSql extends Sql {
public SelectSql(String table, Column[] columns)
@Override public String generate()
}
public class InsertSql extends Sql {
public InsertSql(String table, Column[] columns, Object[] fields)
@Override public String generate()
private String valuesList(Object[] fields, final Column[] columns)
}
public class SelectWithCriteriaSql extends Sql {
public SelectWithCriteriaSql(
String table, Column[] columns, Criteria criteria)
@Override public String generate()
}
public class SelectWithMatchSql extends Sql {
public SelectWithMatchSql(String table, Column[] columns, Column column, String pattern)
@Override public String generate()
}
public class FindByKeySql extends Sql public FindByKeySql(
String table, Column[] columns, String keyColumn, String keyValue)
@Override public String generate()
}
public class PreparedInsertSql extends Sql {
public PreparedInsertSql(String table, Column[] columns)
@Override public String generate() {
private String placeholderList(Column[] columns)
}
public class Where {
public Where(String criteria) public String generate()
}
public class ColumnList {
public ColumnList(Column[] columns) public String generate()
}
- 공개 인터페이스를 전부 SQL 클래스에서 파생하는 클래스로 만들고, 비공개 메서드는 해당 클래스로 옮기고, 공통된 인터페이스는 따로 클래스로 뺐다.
- 기존의 클래스를 건드리지 않아도 된다.