본문 바로가기

JAVA/OOP

단일책임원칙

반응형

단일책임원칙(Single responsibility principle)

객체지향의 기본은 책임을 객체에게 할당하는데 있다.

객체를 객체로 존재하게 하는 이유가 책임인데, 단일책임원칙은 이 책임과 관려된 원칙이다.


- 클래스는 단 한개의 책임을 가져야한다.


클래스가 여러 책임을 갖게되면 그 클래스는 각 책임마다 변경되는 이유가 발생하기 때문이다.

클래스가 한 개의 이유로만 변경되로면 클래스는 한 개의 책임만 가져야한다.

다른말로 클래스를 변경하는 이유는 단 한 개여야 한다.


객체에게 책임을 할당하는 것이 객체 설계의 기본인 만큼, 단일 책임 원칙은 가장 중요한 원칙 중의 하나이다.

단일책임원칙이 잘 지켜지지 않으면 다른 원칙들도 그 효과가 반감되기 때문에 최대한 지켜야하는 원칙이다.

하지만 가장 어려운 원칙이기도 하다.

한개의 책임에 대한 정의가 명확하지 않고, 책임을 도출하기 위해서는 다양한 경험이 필요하기 때문이다.


단일책임원칙위반

display() 메서드는 loadHtml() 메서드에서 읽어온 HTML 응답 문자열을 updateGui()메서드에 보낸다.

updateGui() 메서드는 parseDataToGuiData() 메서드를 이용해 HTML 응답메시지를 GUI에 보여주기 위한 GuiData 객체로 변환 한뒤에 실제 tableUI를 이용해서 데이터를 보여주고 있다.

DataViewer를 잘사용하고 있는데, 데이터를 제공하는 서버가 HTTP 프로토콜에서 소켓 기반의 프로토콜로 변경되었을 경우, 이 프로토콜은 응답 데이터로 byte 배열로 제공한다.

public class DataViewer {

public void display() {

String data = loadHtml();

updateGui(data);

}

public String loadHtml() {

HttpClient client = new HttpClient();

client.connect(url);

return client.getResponse();

}

private void updateGui(String data) {

GuiData guiModel =  parseDataToGuiData(data);

tableUI.changeData(guiModel);

}


private GuiData parseDataToGuiData(String data) {

//파싱 처리 코드

return null;

}

}


데이터를 읽어오는 기능의 변화로 인해 DataViewer는 내부적 변화가 연쇄적으로 발생해야한다.


public class DataViewer {

public void display() {

byte[] data = loadHtml(); //변경 String -> byte[]

updateGui(data);

}

public byte[] loadHtml() {

SocketClient client = new SocketClient();

client.connect(server, port);

return client.read();

}

private void updateGui(byte[] data) { //변경 String -> byte[]

GuiData guiModel =  parseDataToGuiData(data);

tableUI.changeData(guiModel);

}

private GuiData parseDataToGuiData(byte[] data) { //변경 String -> byte[]

//파싱 처리 코드

return null;

}

}


연쇄적인 코드 수정은 두개의 책임(데이터 읽는 책임, 화면에 보여주는 책임)이 한 클래스에 아주 밀접하게 결합되어 있다.

책임의 개수가 많아질수록 한 책임의 기능 변화가 다른 책임에 주는 영향은 비례해서 증가하게 되는데, 이는 결국 코드를 절차지향적으로 만들어 변경을 어렵게 만든다.


데이터 읽기와 화면에 보여주는 책임을 두 개의 클래스로 분리하고 둘 간에 주고받을 데이터를 저수준의 String이 아닌 알맞게 추상화된 타입을 사용하면 데이터를 읽어오는 부분의 변경 때문에 화면을 보여주는 부분의 코드가 변경되는 상황을 막을 수 있게 된다.


책임을 분리하면


DataDisplayer

+display()


DataLoader

+loade() : Data


Data

-someProperty


DataLoader 클래스가 내부적으로 구현을 변경하더라도 DataDisplayer는 영향을 받지 않는다.

한 클래스에 섞여 있던 책임을 두 클래스로 분리함으로써 변경의 여파를 줄일 수 있게 된다.


단일책임원칙을 어길 경우, 재사용을 어렵게 한다는 것이다.

DataViewer 클래스가 HTTP 연동을 위해 HttpClient 라는 패키지를 사용하고, 화면에 데이터를 보여주기 위해 GuiComp 패키지를 사용한다면, 

HttpClient 패키지와 GuiComp 패지키가 각각 별도의 jar 파일로 제공하면, 데이터를 읽어오는 기능이 필요한 DataRequiredClient 클래스를 만들어야 한다.

DataRequiredClient 클래스를 구현하기 위해 필요한 것은 DataViewer 클래스와 HttpClient jar 파일이다.

하지만, 실제로 DataViewer가 GuiComp를 필요하므로 GuiComp jar 파일까지 필요하다.

즉, 실제 사용하지 않는 기능이 의존하는 jar파일까지 필요한 것이다.


단일책임원칙에 따라 책임을 분리한다면, DataRequiredClient 클래스를 구현할때에는 데이터를 읽어오는데 필요한 dataloader 패키지와 HttpClient 패키지만 필요하며, 데이터를 읽어오는 것과 상관없는 GuiComp 패키지나 datadisplay 패키지는 포함시킬 필요가 없어진다.


책임? 변화에 대한것

단일책임원칙을 지키지 않았을 때, 한 책임의 구현변경에 의해 다른 책임과 관련되 코드가 변경될 가능성이 높아진다는 것.

책임의 단위는 변화되는 부분과 관련된다는 의미이다.

DataViewer 클래스에서 데이터를 읽어오는 기능에 변화가 발생했는데, 이런 변화를 통해 데이터를 읽어오는 기능이 별도로 분리되어야 할 책임이라는 것을 알게된다.

각각의 책임은 서로 다른 이유로 변경되고, 서로 다른 비율로 변경되는 특징이 있다.


단일책임원칙을 지키는 방법?

바로 그 메서드를 실행하는 것이 누구인지를 확인해보는 것.


DataViewer 클래스는 display() 메서드와 loadData() 메서드를 제공하는데, GUIApplication은 display() 를 사용하고 DataProcessor는 loadData()를 사용한다고 하자.

GUIApplication이 화면에 표시되는 방식을 변경해야할 경우, 변경되는 메서드는 DataViewer클래스의 display() 메서드다.

반면에 DataProcessor가 읽어오는 데이터를 String이 아닌 다른 타입으로 변경해야 할 경우, DataViewer의 loadData() 메섣는 String이 아닌 DataProcessor가 요구하는 타입으로 변경될 가능성이 높다.

이렇게 클래스의 사용자들이 서로 다른 메서드를 사용한다면, 그들 메서드는 각각 다른 책임에 속할 가능성이 높고, 따라서 책임분리 후보가 될 수 있다.



반응형

'JAVA > OOP' 카테고리의 다른 글

의존역전원칙  (0) 2018.11.06
객체지향  (0) 2018.10.16
절차지향  (0) 2018.10.16
컴포넌트  (0) 2018.04.21
다형성  (0) 2018.04.20