⭐️ SOLID
의미 : 객체 지향 프로그래밍을 하면서 지켜야하는 5대 원칙이다.
목적 : 변경이 용이하고, 유지보수와 확장이 쉬운 소프트웨어를 개발하기 위함이다.
"어떤 클래스를 변경해야 하는 이유는 오직 하나뿐이어야 한다."
하나의 모듈은 한 가지 책임을 가져야 한다는 것으로, 이것은 모듈이 변경되는 이유가 한 가지여야 함을 의미한다. 해당 모듈이 여러 대상 또는 액터들에 대해 책임을 가져서는 안 되고, 오직 하나의 액터에 대해서만 책임을 가져야 한다.
단일 책임 원칙을 제대로 지키면 변경이 필요할 때 수정할 대상이 명확해진다. 그리고 이러한 장점은 시스템이 커질수록 극대화되는데, 시스템이 커지면서 서로 많은 의존성을 갖게되는 상황에서 변경 요청이 오면 딱 1가지만 수정하면 되기 때문이다.
한 사람이 두가지 일을 모두 책임지려고 하고 있다.
public class Person {
public String job;
public Person(String job)
{
this.job = job;
}
public void Work()
{
if(job.equals("Programmer"))
System.out.println("코딩하다");
else if(job.equals("Teacher"))
System.out.println("수업을 하다.");
}
}
역할에 따라서 하나의 책임만 가져간다.
public abstract class Person {
abstract public void Work();
}
public class Programmer extends Person {
public void Work()
{
System.out.println("개발을 하다");
}
}
public class Teacher extends Person{
public void Work()
{
System.out.println("학생을 가르치다");
}
}
확장에 대해 열려있고, 수정에 대해서는 닫혀있어야 한다.
원칙을 지키기 위해,
대표적으로 JDBC 인터페이스를 이용하여 여러 데이터베이스를 갈아끼울 수 있다.
개방 폐쇄 원칙을 지키기 위해서는 추상화에 의존해야 한다. 추상화란 핵심적인 부분만 남기고, 불필요한 부분을 제거함으로써 복잡한 것을 간단히 하는 것이다. 변하지 않는 부분은 고정하고 변하는 부분을 생략하여 추상화함으로써 (인터페이스 이용) 변경이 필요한 경우 생략된 부분을 수정하여 (구현체 변경) 개발 폐쇄의 원칙을 지킬 수 있다.
이는 결국 런타임 의존성과 컴파일타임 의존성에 대한 이야기이다. 여기서 런타임 의존성이란 애플리케이션 실행 시점에서의 객체들의 관계를 의미하고, 컴파일타임 의존성이란 코드에 표현된 클래스들의 관계를 의미한다. 다형성을 지원하는 객체 지향 프로그래밍에서 런타임 의존성과 컴파일타임 의존성은 동일하지 않다.
컴파일 시점에는 추상화된 인터페이스에 의존하고 있지만 런타임 시점에는 구체 클래스에 의존하게 된다.
객체가 알아야 할 지식이 많으면 결합도가 높아지고, 결합도가 높아질수록 개방 폐쇄 원칙을 따르는 구조를 설계하기가 어려워진다.
추상화를 통해 변하는 것들은 숨기고 변하지 않는 것들에 의존하게 하려고 하자.
"서브 타입은 언제나 자신의 기반 타입 (부모) 으로 교체할 수 있어야 한다."
상속 관계에서 자식 클래스는 최소한 자신의 부모 클래스에서 가능한 행위는 수행할 수 있어야 한다는 개념이다. 즉, 객체의 상속관계에서 자식 클래스는 언제든 부모 타입으로 교체할 수 있다는 말을 뜻한다.
객체를 호출하면서 클라이언트는 부모의 어떤 자식이 올 지는 모르지만 최소한으로 기대하는 기능이 있을 것이다. 자식은 이러한 기능은 만족시켜줄 수 있어야한다. 자식의 다양한 기능을 클라이언트가 예상하지 못할 수 있으므로 추상화 레벨을 맞춰서 메소드 호출이 불가능하도록 하거나 해당 추상화 레벨에 맞게 메소드를 오버라이딩 하는게 합리적일 것이다.
Now when we look at how each shape would draw based on their dimensions, we can see how they can each be passed around and act as their parent class.
class Shape
def initialize(dimension*)
@dimension = dimension
end
end
class Circle < Shape
def initialize(dimension)
@radius = dimension
end
end
class Rectangle < Shape
def initialize(dimension_1, dimension_2)
@height = dimension_1
@width = dimension_2
end
end
class Square < Shape
def initialize(dimension)
@side_length = dimension
end
end
"클라이언트는 자신이 사용하지 않는 메서드에 의존 관계를 맺으면 안된다."
각 역할에 맞게 인터페이스를 분리한다. 즉 클라이언트의 목적과 용도에 맞는 기능만을 구현하는 인터페이스를 제공해야 한다.
인터페이스 분리 원칙을 지키기 위해, 어떤 구현체에 부가 기능이 필요하다면 이 인터페이스를 구현하는 다른 인터페이스를 만들어서 해결할 수 있다.
클라이언트에 따라 인터페이스를 분리하면 변경에 대한 영향을 더욱 세밀하게 제어할 수 있다.
Car 클래스에 오토드라이빙 메서드가 있지만 이 기능을 사용하지 않는 구현체가 있을 수 있다. 이러한 경우 인터페이스를 세부화하여 나눠주는 것이 원칙에 맞다.
public interface Car {
String autoDrive();
String autoParking();
String drive();
String break();
}
public class Ray implements Car {
//해당 기능을 사용 안하고싶음!!!!!
@Override
public String autoDrive() {
return "";
}
//해당 기능을 사용 안하고싶음!!!!!
@Override
public String autoParking() {
return "autoParking";
}
@Override
public String drive() {
return "drive";
}
@Override
public String break() {
return "break";
}
}
인터페이스를 나누어서 각자 꼭 필요한 기능만을 가지게 한다.
public interface Car {
String drive();
String break();
}
public interface ElectricCar {
String autoDrive();
String autoParking();
}
그리고 필요에 따라서 인터페이스를 상속하는 것이 좋다.
public class Telsa implements Car,ElectricCar {
@Override
public String autoDrive() {
return "autoDrive";
}
@Override
public String autoParking() {
return "autoParking";
}
@Override
public String drive() {
return "drive";
}
@Override
public String break() {
return "break";
}
}
public class Ray implements Car {
@Override
public String drive() {
return "drive";
}
@Override
public String break() {
return "break";
}
}
"고수준 모듈은 저수준 모듈에 의존하면 안된다. 이 두 모듈 모두 다른 추상화된 것에 의존해야한다. 즉, 자신보다 변하기 쉬운것에 의존하지 마라"
의존 관계를 맺을 때 변화하기 쉬운 것보다는 변화하기 어려운 것에 의존하라는 원칙이다. 여기서 변화하기 어려운 것이란 추상적인 객체 (interface, abstract)를 말한다.
의존 역전 원칙은 개발 폐쇄 원칙과 밀접한 관련이 있으며, 의존 역전 원칙이 위배되면 개발 폐쇄 원칙 역시 위배될 가능성이 높다. 의존 역전 원칙에서 의존성이 역전되는 시점은 컴파일 시점이다. 런타임 시점에는 구체 클래스에 의존한다. 하지만 의존 역전 원칙은 컴파일 시점 도는 소스 코드 단계에서의 의존성이 역전되는 것을 의미하며, 코드에서는 인터페이스에 의존한다.
자동차가 구체적인 타이어가 아닌 추상화된 타이어 인터페이스에만 의존하게 함으로써 타이어가 변경되어도 자동차가 영향을 받지 않느다.
참고 :
https://mangkyu.tistory.com/194
https://devlog-wjdrbs96.tistory.com/380
https://jeongkyun-it.tistory.com/108
http://daisymolving.github.io/2016/03/18/liskov-substitution-principle.html