표준 자바 관례에 따르면, 클래스의 구성은
<변수목록>
static public 변수 -> static private 변수 -> private 인스턴스 변수
(공개 변수가 필요한 경우는 거의 없다)
<공개 함수>
(비공개 함수는 자신을 호출하는 공개 함수 직후에 넣는다.)
순으로 이루어진다.
클래스를 만들 때 첫 번째 규칙은 작아야한다. 두 번째 규칙은 더 작아야한다!
(함수 단원과 같은 말)
아래 코드는 잘못된 예시를 보여준다. 무슨말인지 알아볼 수 없는 클래스이다.
public class SuperDashboard extends JFrame implements MetaDataUser {
public String getCustomizerLanguagePath()
public void setSystemConfigPath(String systemConfigPath)
public String getSystemConfigDocument()
public void setSystemConfigDocument(String systemConfigDocument)
public boolean getGuruState()
public boolean getNoviceState()
public boolean getOpenSourceState()
public void showObject(MetaObject object)
public void showProgress(String s)
public boolean isMetadataDirty()
public void setIsMetadataDirty(boolean isMetadataDirty)
public Component getLastFocusedComponent()
public void setLastFocused(Component lastFocused)
public void setMouseSelectState(boolean isMouseSelected)
public boolean isMouseSelected()
public LanguageManager getLanguageManager()
public Project getProject()
public Project getFirstProject()
public Project getLastProject()
public String getNewProjectName()
public void setComponentSizes(Dimension dim)
public String getCurrentDir()
public void setCurrentDir(String newDir)
public void updateStatus(int dotPos, int markPos)
public Class[] getDataBaseClasses()
public MetadataFeeder getMetadataFeeder()
public void addProject(Project project)
public boolean setCurrentProject(Project project)
public boolean removeProject(Project project)
public MetaProjectHeader getProgramMetadata()
public void resetDashboard()
public Project loadProject(String fileName, String projectName)
public void setCanSaveMetadata(boolean canSave)
public MetaObject getSelectedObject()
public void deselectObjects()
public void setProject(Project project)
public void editorAction(String actionName, ActionEvent event)
public void setMode(int mode)
public FileManager getFileManager()
public void setFileManager(FileManager fileManager)
public ConfigManager getConfigManager()
public void setConfigManager(ConfigManager configManager)
public ClassLoader getClassLoader()
public void setClassLoader(ClassLoader classLoader)
public Properties getProps()
public String getUserHome()
public String getBaseDir()
public int getMajorVersionNumber()
public int getMinorVersionNumber()
public int getBuildNumber()
public MetaObject pasting(MetaObject target, MetaObject pasted, MetaProject project)
public void processMenuItems(MetaObject metaObject)
public void processMenuSeparators(MetaObject metaObject)
public void processTabPages(MetaObject metaObject)
public void processPlacement(MetaObject object)
public void processCreateLayout(MetaObject object)
public void updateDisplayLayer(MetaObject object, int layerIndex)
public void propertyEditedRepaint(MetaObject object)
public void processDeleteObject(MetaObject object)
public boolean getAttachedToDesigner()
public void processProjectChangedState(boolean hasProjectChanged)
public void processObjectNameChanged(MetaObject object)
public void runProject()
public void setAçowDragging(boolean allowDragging)
public boolean allowDragging()
public boolean isCustomizing()
public void setTitle(String title)
public IdeMenuBar getIdeMenuBar()
public void showHelper(MetaObject metaObject, String propertyName)
}
위의 코드를 아래와 같이 고칠수도 있다.
public class SuperDashboard extends JFrame implements MetaDataUser {
public Component getLastFocusedComponent()
public void setLastFocused(Component lastFocused)
public int getMajorVersionNumber()
public int getMinorVersionNumber()
public int getBuildNumber()
}
한 눈에 보면 상당히 깔끔해보인다. 하지만 책임이 너무 많다.
클래스나 모듈을 변경할 이유는 단 하나뿐이어야 한다.
객체지향에서 가장 중요한 개념이란 것을 모두가 알고 있지만 잘 지켜지지 않는다.
-> '소프트웨어가 돌아가게 만드는 활동'과 '소프트웨어를 깨끗하게 만드는 활동'은 완전히 별개이기 때문
많은 개발자들이 단일 책임 클래스가 많아지면, 큰 그림을 이해하기 어려워진다고 우려한다. 하지만 작은 클래스가 많은 시스템이든, 큰 클래스로 구성된 시스템이든 부품의 수는 비슷하다
-> 도구 상자를 어떻게 정리할 것인가의 차이이다.
(작은 서랍을 많이 두고 나눠 넣고 싶은가? 큰 서랍 몇 개에 다 넣고 싶은가)
대다수의 시스템은 지속적인 변경이 가해진다. 그리고 변경할때마다 시스템이 의도대로 동작하지 않을 위험이 따른다.
-> 하지만 깨끗한 시스템은 클래스를 체계적으로 정리해 변경에 수반하는 위험을 낮춘다.
아래코드처럼 sql class가 있다고 하자. 아직 미완성이라서 update 같은 기능을 지원하지 않기에 분명 수정해야 할 때가 온다.
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)
}
변경할 때가되면 위와 같은 코드로는 변경이 쉽지않다.
아래와 같이 고친다면 변경이 쉬워질 것이다.
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 String generate() {
}
public class ColumnList {
public ColumnList(Column[] columns) public String generate()
public String generate() {
}
클래스가 서로 분리되었기 때문에 클래스가 단순하고 코드는 순식간에 이해할 수 있게 변했다.
class 의 종류에는 Concrete(구체적인) class, Abstract(추상적인) class가 있다.
Concrete class는 구현이 바뀔 때 위험에 빠지기 때문에 항상 interface, Abstract class를 사용해서 구현이 미치는 영향과 격리해야 한다.
예를 들어 Protfolio 클래스를 만든다고 가정하자.
-> Portfolio 클래스에서 API를 직접 호출하지 않고, StockExchange라는 interface를 생성해서 이를 구현하는 TokyouStockExchange 클래스를 구현한다
public insterface StockExchange {
Money currentPrice(String symbol);
}
-> Portfolio 생성자에서 StockExchange를 인수로 받도록 한다.
public Portfolio {
private StockExchange exchange;
public Portfolio(StockExchange exchange) {
this.exchange = exchange;
}
// ...
}
-> 이제 TokyoStockExchange 클래스를 흉내내는 테스트용 클래스를 만들 수 있어진다.
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());
}
}
테스트가 가능할 정도로 시스템의 결합도를 낮추면 유연성과 재사용성은 더 높아진다