🔖 오늘 읽은 범위 : 10장, 클래스
이전 내용 복습
*- 클래스를 만들 때 첫 번째 규칙은 크기다. 클래스는 작아야 한다
- 일반적으로 메서드가 변수를 더 많이 사용할수록 메서드와 클래스는 응집도가 더 높다*
큰 함수를 작은 함수 여럿으로 나누기만 해도 클래스 수가 많아진다.
불행히도 이렇게 하면 클래스가 응집력을 잃는다. 몇몇 함수만 사용하는 인스턴스 변수가 점점 더 늘어나기 때문이다.
그런데 잠깐만! 몇몇 함수가 몇몇 변수만 사용한다면 독자적인 클래스로 분리해도 되지 않는가? 당연하다. 클래스가 응집력을 잃는다면 쪼개라!
결론
- 큰 함수를 작은 함수 여럿으로 쪼개라
- 몇몇 인스턴스 변수와 함수를 묶어 독자적인 클래스로 분리하라
PrintPrimes 프로그램 리팩터링
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)
}
selectWithCriteria
라는 비공개 메서드가 있는데, 이 메서드는 select문을 처리할 때만 사용한다.abstract public class Sql {
public Sql(String table, Column[] columns)
abstract public Strign 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 Coiumn[] columns)
}
public class SelectWithCriteriaSql extends Sql {
public SelectWithCriteriaSql(String table, Column[] columns, Criteria criteria)
@Override public String generate()
}
public class FindByKeySql extends Sql {
public FindByKeySql(String table, 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 ColumnLsit(Column[] columns)
public String generate()
}
요구사항은 변하기 마련이다. 따라서 코드도 변하기 마련이다
객체 지향 프로그래밍 입문에서 우리는 구체적인(concreate) 클래스와 추상(abstract) 클래스가 있다고 배웠다.
상세한 구현에 의존하는 클라이언트 클래스는 구현이 바뀌면 위험에 빠진다. 그래서 우리는 인터페이스와 추상 클래스를 사용해 구현이 미치는 영향을 격리한다.
상세한 구현에 의존하는 코드는 테스트 하기가 어렵다.
Portfolio 클래스 리펙터링
public interface StackExchange {
Money currentPrice(String symbol);
}
Portfolio 클래스에서 TokyoStockExchange API를 직접 호출하는 대신 Stock Exchange라는 인터페이스를 생성한 후 메서드 하나를 선언한다.
public Portfolio {
private StockExchage exchange;
public Portfolio(StockExchange exchage) {
this.exchange = exchange;
}
}
다음으로 StockExchange 인터페이스를 구현하는 TokyoStockExchange 클래스를 구현한다.
또한 Portfolio 생성자를 수정해 StockExchange 참조자를 인수로 받는다.
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");
Assert.assertEquals(500, portfolio.value());
}
}
이제 TockyoStockExchange 클래스를 흉내내는 테스트용 클래스를 만들 수 있다.
우리 테스트용 클래스는 단순히 미리 정해놓은 표 값만 참조한다. 그러므로 우리는 전체 포트폴리오 총계가 500불인지 확인하는 테스트 코드를 작성할 수 있다.
위와 같은 테스트가 가능할 정도로 시스템의 결합도를 낮추면 유연성과 재사용성도 더욱 높아진다. 결합도가 낮다는 소리는 각 시스템 요소가 다른 요소로 부터 그리고 변경으로부터 잘 격리되어 있다는 의미다.
이렇게 결합도를 최소로 줄이면 자연스럽게 또 다른 클래스 설계 원칙인 DIP(Dependency Inversion Principle)를 따르는 클래스가 나온다. 본질적으로 DIP는 클래스가 상세한 구현이 아니라 추상화에 의존해야 한다는 원칙이다.
결론
- 우리가 개선한 Portfolio 클래스는 TokyoStockExchange라는 상세한 구현 클래스가 아니라 StockExchange 인터페이스에 의존한다.
- 이와 같은 추상화로 실제로 주가를 얻어오는 출처나 얻어오는 방식 등과 같은 구체적인 사실을 모두 숨긴다