## 목차 ##



## 클래스 체계 ##

  • 표준 자바 관례에 따라 아래와 같은 순서로 변수 및 함수가 정의된다.

    • 변수

      • static public final
      • static private final
      • static public
      • static private
      • public
      • private
    • 함수

      • static
      • public
      • private
      • private 함수는 자신을 호출하는 public 함수 다음에 넣는다.
  • 캡슐화

    • 변수 및 유틸리티 함수는 가능한 숨기는게 좋다.
    • 테스트 코드가 함수나 변수를 사용해야하면 protected로 선언하거나 패키지 전체로 공개한다.
    • 하지만 그 전에 비공개 상태 유지를 위한 온갖 방법을 강구해야 마땅하다.
    • 캡슐화를 풀어주는 것은 언제나 최후의 결정이다.



## 클래스는 작아야 한다! ##

  • 클래스 설계의 기본 규칙은 '작게!'이다.

  • 클래스 이름은 클래스의 책임을 기술한다.

    • 작명은 클래스 크기를 줄이는 첫 번째 관문이다.
    • 간결한 이름이 떠오르지 않는 이유는 클래스 크기가 너무 커서이다.
    • 클래스 이름이 모호하다면 클래스 책임이 너무 많아서이다.
  • 단일 책임 원칙(SRP)

    • 클래스나 모듈의 변경 이유는 '단 하나' 뿐이어야 한다는 원칙
    • 왜냐하면 클래스의 책임은 '단 하나'여야하므로
    • 클래스 세부 분리는 큰 그림을 그리는데 오히려 방해? -> 어차피 익혀야 할 내용은 같다.
    • 공구를 큰 서랍 여러개에 뭉태기로 던져놓을것인가? 작은 서랍에 기능과 이름이 명확한 컴포넌트별로 나눠놓을것인가?
    • 클래스 크기, 책임이 작으면 코드를 변경할 때 영향이 미치는 컴포넌트만 이해해도 충분하다.
  • 응집도(Cohension)

    • 하나의 프로그램을 구성하는 각각의 모듈이 그 고유의 기능을 잘 처리할 수 있는지를 나타내는 정도
    • 이상적인 객체지향 설계를 위한 단 하나의 원칙 : 응집도는 높이고 결합도는 낮춰야한다.
  • 응집도가 높은 클래스

    • 클래스의 모든 메소드가 인스턴스 변수 하나 이상 사용해야한다.
    • 메소드가 인스턴스 변수를 많이 사용할수록 응집도는 높다.
    • 응집도가 높다는 것은 클래스의 변수 및 메소드가 서로 의존하며 논리적인 단위로 묶인다는 의미이다.
    • 몇몇 메소드만 사용하는 인스턴스 변수가 많아진다는 것은 새로운 클래스로 쪼개야한다는 신호이다.
  • 함수를 쪼개다보면 여러 클래스가 나온다.

    • 큰 함수를 작은 함수 여럿으로 나눌때 공동으로 사용하는 로컬변수는 인스턴스 변수로 승격시킨다.
    • 이렇게 하다보면 몇몇 함수만 사용하는 인스턴스 변수가 많아지고 클래스는 응집도를 잃어간다.
    • 그 몇몇 함수와 인스턴스 변수를 가지고 새 클래스를 작성하면 되는 것이다!


public class SuperDashboard extends JFrame implements MetaDataUser {
    public Component getLastFocusedComponent()
    public void setLastFocused(Component lastFocused)
    public int getMajorVersionNumber()
    public int getMinorVersionNumber()
    public int getBuildNumber() 
}

위 메소드는 마지막으로 포커싱된 컴포넌트를 관리하고 시스템 버전도 관리한다. 이 모듈을 변경할 이유가 2가지(버전이 바뀌는경우, 포커싱 로직이 변경되는 경우)이므로 SRP를 위반한다.

아래와 같이 버전과 관련된 기능을 따로 빼내었다. 이제는 각 클래스는 하나의 책임만 진다. 클래스를 재설계함으로써 재사용성이 높아지며 클래스 응집도가 높아졌다.

// Version 기능 분리
public class Version {
    public int getMajorVersionNumber() 
    public int getMinorVersionNumber() 
    public int getBuildNumber()
}



## 변경하기 쉬운 클래스 ##

  • 클래스를 체계적으로 정리하면 시스템 변경에 따른 위험을 낮출 수 있다.
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을 다루는 인터페이스 코드이다. update문과 같은 새로운 기능을 지원하려면 클래스에 손대야한다. 여러번 말했듯 이렇게 되면 다른 코드를 망가뜨릴 위험성이 도사리고있다. 따라서 테슽트도 완전히 다시해야한다.

아래와 같이 공개 인터페이스를 각각 Sql 클래스의 파생 클래스로 만들어 분리해보자.

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()
}

각각의 기능별로 클래스를 분리했다. 분리 후 보이지 않는 valueList(), selectWithCriteria(), placeholderList()와 같은 비공개 메소드들은 이들을 사용하는 파생 클래스로 옮겼다.

무엇이 더 좋아졌는가?

(a) 함수 하나를 수정해도 다른 코드에 영향이 없다.
(b) 새로운 기능을 추가할 때 기존 클래스 변경이 필요없다. 다른코드가 망가질 위험도 없다.
(c) SRP를 만족한다.(각 클래스가 하나의 책임만 진다.)
(d) OCP를 만족한다.(확장에는 개방되어있고 수정에는 폐쇄적이다. 새로운 클래스를 끼워넣기만 하면된다.)


  • 변경으로부터 격리
    • 상세한 구현 클래스에 의존하는 코드는 테스트가 어렵다.
    • 인터페이스, 추상클래스를 통해 상세 구현이 미치는 영향을 격리하는 이유가 이것이다.
    • 실제 서비스에서는 구현 클래스 객체를 생성하고 테스트나 임시적인 사용에서는 테스트용 객체를 생성해서 사용할 수 있다.
    • 이렇게 하면 계속해서 값이 변하는 외부 API를 사용하는 시스템이더라도 테스트용 API를 만들어 테스트가 쉬워진다.
    • 결합도를 낮춘다.



TokyoStockExchange API를 사용해 포트폴리오 값을 계산하는 시스템이 있다. 이 API는 5분마다 주가가 반영되어 값이 변한다. 이러한 API를 사용해 테스트용 코드를 짜기는 어렵다.

따라서 Porfolio클래스에서 TokyoStockExchange를 직접 참조하는 대신 StockExchange 인터페이스를 통해 TokyoStockExchange 클래스의 직접적인 영향으로부터 격리시킨다.

public interface StockExchange { 
    Money currentPrice(String symbol);
}

-----------------------------------

public Portfolio {
    private StockExchange exchange;
    public Portfolio(StockExchange exchange) {
        this.exchange = exchange; 
    }
    // ... 
}

이렇게 하면 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()); 
    }
}

시스템의 결합도가 낮아져 유연성, 재사용성이 더욱 높아졌다. 결합도가 낮다는 건 각 시스템 요소가 다른 요소, 변경으로부터 잘 격리되어있다는 의미다.

+ Recent posts