OOP Solid
객체지향 프로그래밍 Solid 에 대해서 포스팅합니다.
Solid는 2000년대 초반에 로버트 마틴이 제시한 객체지향 프로그래밍 원칙의 첫 글자로 만들어진 단어입니다.
프로그래밍을 할때 기본이 되는 사상을 담고 있으므로 체화 하는 것이 중요합니다.
각각의 단어는 아래와 같은 의미를 갖고 있습니다.
- S: 단일 책임 원칙(Single Responsibility Principle)
- O: 개방-폐쇄 원칙(Open Closed Principle)
- L: 리스코프 치환 원칙(Liskov Substitution Principle)
- I: 인터페이스 분리 원칙(Interface Segregation Principle)
- D: 의존 역전 원칙(Dependency Inversion Principle)
원칙의 이름만 보고 그 의미를 파악하기 어렵습니다. 자세한 내용에 대해 설명하겠습니다.
단일 책임 원칙 (Single Responsibility Principle)
같은 이유로 변경될 코드들은 모으고. 다른 이유로 변경될 코드들은 흩어라.
SRP란 Single Responsibility Principle라는 단일 책임 원칙을 의미하며 말 그대로 단 하나의 책임을 가져야 한다는 것을 의미합니다. 여기서 말하는 책임의 기본 단위는 객체를 의미합니다. 하나의 객체가 하나의 책임을 가져야 한다는 의미입니다. 여기서 책임은 객체가 할 수 있는 것과 해야 하는 것 으로 나뉘어져 있습니다. 즉 한 마디로 요약하자면 하나의 객체는 자신이 할 수 있는 것과 해야하는 것만 수행 할 수 있도록 설계되어야 한다는 법칙입니다.
이는 응집성과 관련이 있습니다. 하나의 클래스에서 여러 기능을 담당하게 되면 그 클래스의 응집력이 올라가게 됩니다. 그렇게 되면 코드의 관리, 기능 추가(변경)에 대해서 어려워 질 수 밖에 없습니다.
아래 코드를 예시로 보겠습니다
public class Empolyee {
public void getTask(){ }
public void addTask() { }
public void save(){ }
public Empolyee load() { }
public void printTaksCard() { }
public void printAttendance() { }
}
위 클래스는 직원이
- 업무 정보를 획득하고
- 업무를 추가하고
- 변경 된 업무 대해서 저장하고,
- 직원 정보를 로드하고
- 직원 정보에 대해서 출력합니다
간단히 살펴봐도 직원이 할 수있는 역할에 비해 많은 것을 할 수 있는것을 확인 할 수 있습니다. 이렇게 코드를 구성하게 되면 변경에 관점에서 문제가 생기게 됩니다.
잘 설계된 프로그램은 새로운 요구사항이나 변경이 있을 때 가능한 영향 받는 부분을 최소화해야 합니다. 하지만 위 클래스는 업무의 정보, 업무의 데이터베이스 저장, 업무의 출력까지 많은 것을 책임지고 있습니다. 이렇게 되면 변경이 있을 때마다 해당 클래스에 접근해서 수정을 해줘야합니다.
뿐만 아니라 클래스 내부에서 서로 다른 역할을 수행하는 코드끼리 강하게 결합되게 됩니다. 이렇게 되면 수정이 발생 했을 때 정상동작 확인을 위해 코드 전체를 확인해야한다는 문제가 발생합니다. 즉 유지보수의 관점에서 문제가 생긴다는 의미입니다. 이러한 코드를 응집력이 높은 코드라고 하는데 SRP는 이러한 응집력을 낮추고 유사한 역할끼리의 결합력을 올리라는 의미가 됩니다.
그렇다면 SRP를 적용한 클래스는 어떤 모양일까요. 우선 Empolyee 를 단순화 하면 아래와 같습니다.
public class Empolyee {
public void getTask(){ }
public void addTask() { }
}
위 클래스를 보면 Empolyee는 단순히 Task에 대한 정보만 다루고 있는것을 확인 할 수 있습니다.
그리고 Empolyee에 대해서 데이터베이스에 저장하는 DAO 객체에 대해서 분리할 수 있습니다.
public class EmpolyeeDAO {
public Empolyee load() { }
public void save(Empolyee empolyee) { }
}
마지막으로 Empolyee의 정보를 출력해주는 HR 클래스를 만들 수 있습니다.
public class HR {
public void printTaksCard(Empolyee empolyee) { }
public void printAttendance(Empolyee empolyee) { }
}
이렇게 되면 각각의 클래스는 하나의 책임만 갖게 되고 변경 시 다른 클래스에 영향을 미치지 않게 됩니다. 정리하면 모듈에 단일책임으로 부여하여 이를 사용하게 할 수 있도록 함으로써 SRP를 지킬 수 있습니다.
개방-폐쇄 원칙 (Open-Closed Principle)
모듈은 확장에 열려있고, 변경에는 닫혀있어야 한다.
Open-Closed Principle 는 기본적으로 기능을 추가할 때 기존 코드를 건들이지 않는 구조를 의미합니다.
위의 SRP와 마찬가지로 유지보수, 기능추가의 관점에서 이득을 얻기 위한 원칙입니다.
예를 들어 위의 EmpolyeeDAO를 구현할 때 load를 하는 database가 추가되는 상황이라고 가정해봅시다.
public class EmpolyeeDAO {
public Empolyee load() { }
public void save(Empolyee empolyee) { }
}
기존 위의 class를 그대로 사용할시 load와 save의 코드를 반드시 변경해야 할 것으로 예상됩니다.
public interface EmpolyeeDao {
public Empolyee load() { }
public void save(Empolyee empolyee) { }
}
public class EmpolyeeMysqlDao implement EmpolyeeDao {
public override Empolyee load() { }
public override void save(Empolyee empolyee) { }
}
public class EmpolyeeMongoDBDao implement EmpolyeeDao {
public override Empolyee load() { }
public override void save(Empolyee empolyee) { }
}
EmpolyeeDao mysqlDao = new EmpolyeeMysqlDao()
EmpolyeeDao mongoDao = new EmpolyeeMongoDBDao()
이때 위와 같이 상속을 통해 클래스를 분리하게 되면 새로운 데이터베이스가 추가되어도 기존 클래스는 그대로 유지하면서 기능추가가 가능하게 됩니다.
리스코프 치환 원칙 (Liskov Substitution Principle)
어떤 인터페이스를 사용하는 프로그램은 그 인터페이스의 구현체(implementation)에 의해 동작이 오락가락하면 안된다.
Liskov Substitution Principle 원칙을 정리해서 말하자면 상속된 클래스를 부모 클래스를 이용하여 호출할 때 부모의 인터페이스와 전혀 상관 없는 동작이 일어나면 안된다는 의미입니다. 즉 상속 받은 클래스는 부모 클래스의 책임을 모두 수행할 수 있어야한다는 의미입니다. 위의 EmpolyeeDao를 기준으로 봤을 때, EmpolyeeMysqlDao 의 구현 중 load 함수를 전혀 엉뚱한 삭제의 역할 을 한다거나 하는 의미입니다. load에서는 부모의 역할에 맞게 데이터의 load만 일어나야 합니다.
인터페이스 분리 원칙 (Interface Segregation Principle)
사용자가 필요하지 않은 것들에 의존하게 되지 않도록, 인터페이스를 작게 유지하라.
Interface Segregation Principle 원칙은 단일 책임의 원칙과 유사합니다만, 사용자의 관점이 들어간다는 것이 조금 다릅니다. 즉 사용자가 해당 클래스를 사용할때 불필요한 인터페이스를 노출하지 않는다는 의미가 되고 결국 클래스에 대한 책임을 분리하는 것에 해당합니다.
사용자가 작성 된 클래스를 사용할때 방대한 양의 인터페이스가 존재하면 클래스의 사용성이 저해됩니다. 따라서 노출이 필요한 클래스만 노출해서 사용자가 쉽게 클래스를 사용할 수 있어야한다는 의미가 됩니다.
Comments