자바 표준 클래스 정의에 따른 변수 순서
: 추상화 단계가 순차적으로 내려가게 짜여진다
변수와 유틸리티 함수는 가능한 공개하지 않는 편(protected로 선언)이 낫지만 반드시 숨겨야 한다는 법칙도 없다. 때때로 필요하다면 캡슐화를 풀기도 한다. 하지만 그 전에 비공개 상태를 유지할수 있는 방법을 찾고, 캡슐화를 푸는 것은 최후의 수단으로 한다.
클래스를 만들 때 첫 번째 규칙은 '크기' 다. 클래스는 작아야 한다.
클래스의 크기는 '맡은 책임의 수' 를 기준으로 측정한다. 메서드의 개수와는 관계가 없다.
💻 너무 많은 책임을 가진 클래스
모든 메서드를 담아 만능 클래스라고 할 수 있지만, 이렇게 한 클래스에 책임이 많을 경우 유지보수에 어려움을 겪는다.
public class SuperDashboard extends JFrame implements MetaDataUser { public String getCustomizerLanguagePath() public void setSystemConfigPath(String systemConfigPath) public String getSystemConfigDocument() public Properties getProps() public String getUserHome() public String getBaseDir() public int getMajorVersionNumber() public int getMinorVersionNumber() public int getBuildNumber() // 외 70개의 메서드 }
💻 5개의 메서드를 가진 클래스
이렇게 적은 양의 메서드만 포함하더라도, 여러개의 책임을 갖고 있기 때문에 좋지 못하다.
메서드의 수와 책임은 관계가 없다.public class SuperDashboard extends JFrame implements MetaDataUser { public Component getLastFocusedComponent() public void setLastFocused(Component lastFocused) public int getMajorVersionNumber() public int getMinorVersionNumber() public int getBuildNumber() }
위의 5개의 메소드를 가진 클래스를 보면 Super라는 단어를 클래스 이름으로 하여 어떤 책임을 맡고 있는지 알기 어렵다. 메소드를 보면 하나는 프로그램의 소프트웨어 버전 정보를 추적하고, 또 하나는 JFrame을 상속받아 스윙 컴포넌트를 관리한다.
-> 프로그램의 코드가 변경될 때마다 소프트웨어 버전이 바뀐다고 했을 때, 이 버전정보는 SuperDashboard에 국한된 동작은 아니라는 것이다.
💻 단일 책임 클래스
버전 정보를 다루는 메서드 추출 : 재사용성 증가
public class Version { public int getMajorVersionNumber() public int getMinorVersionNumber() public int getBuildNumber() }
이렇게 변경할 이유를 파악하게 되면 추상화가 더 쉬워진다.
큰 클래스 몇 개 보다 작은 클래스 여럿인 시스템이 더 바람직하다.
작은 클래스는 맡은 책임이 하나며, 변경할 이유가 하나며, 다른 작은 클래스들과 협력해 시스템을 동작한다.
응집도가 높아질수 있도록 변수와 메소드를 적절하게 분리하여 새로운 클래스로 쪼개준다.
💻 응집도가 높은 클래스
- 클래스의 메소드들이 클래스의 인스턴스 변수를 하나 이상 사용하고 있는지 확인
- 응집도를 더 높이기 위해서 일부의 메소드에서만 사용되는 인스턴스 변수가 있는지 확인
- 응집도가 있는 클래스를 응집도가 더 높은 2개이상의 다른 클래스들로 쪼갤 수 있다.
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; } }
큰 함수를 작은 함수로 쪼갤 때 필요한 변수들을 클래스 인스턴스로 바꾸면 함수를 쪼개기 쉬워진다. 하지만 그만큼 한 함수에 몇몇 변수들만 사용하기 떄문에 클래스가 응집도가 낮아진다.
응집도가 낮아지면 한 클래스가 아니라 여러 클래스로 쪼갠다.
클래스를 쪼개게 되면, 같은 동작을 하더라도 아래와 같은 이유로 프로그램의 길이가 길어질 수 있다.
요구사항이 변하면 코드가 변하기 쉽다. 그래서 인터페이스와 추상 클래스를 사용해 구현이 미치는 영향을 격리한다. 결합도를 낮춰서 유연성과 재사용성을 높일 수 있다.
Porfolio 클래스
: 외부 TokyoStockExchange API를 사용해 값을 계산, 5분마다 변하는 값 때문에 test 코드 짜는데 어려움
public insterface StockExchange { Money currentPrice(String symbol); }
public Portfolio { private StockExchange exchange; public Portfolio(StockExchange exchange) { this.exchange = exchange; } // ... }
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()); } }
낮은 시스템 결합도
추상 클래스와 인터페이스를 잘 활용하여 변경하기 쉬운 클래스를 만들어라.