wikipedia(https://en.wikipedia.org/wiki/SOLID)를 chat gpt한테 번역해달라고 해보았다.
소프트웨어 공학에서 SOLID는 객체 지향 설계를 이해하기 쉽고 유연하며 유지 보수하기 쉽게 만들기 위해 고안된 다섯 가지 설계 원칙을 나타내는 메모닉 약어입니다.
SOLID 아이디어는 다음과 같습니다.
SRP[Single-responsibility principle, 단일 책임 원칙]: "한 클래스가 변경되는 이유는 오직 하나여야 합니다." 다시 말해, 모든 클래스는 하나의 책임만을 가져야 합니다.
OCP[Open–closed principle, 개방-폐쇄 원칙]: "소프트웨어 개체는 확장을 위해 열려 있어야 하지만 수정을 위해 닫혀 있어야 합니다."
LSP[Liskov substitution principle, 리스코프 치환 원칙]: "기본 클래스에 대한 포인터나 참조를 사용하는 함수는 파생 클래스의 객체를 사용할 수 있어야 합니다."
ISP[Interface segregation principle, 인터페이스 분리 원칙]: "클라이언트는 사용하지 않는 인터페이스에 의존하게 해서는 안됩니다."
DIP[Dependency inversion principle, 의존성 역전 원칙]: "추상화에 의존해야 하며 구체화에 의존해서는 안 됩니다."
SOLID 원칙은 모든 객체 지향 설계에 적용될 수 있지만, 애자일 개발이나 적응형 소프트웨어 개발과 같은 방법론에도 핵심 철학을 형성할 수 있습니다.
쉽게 알려줄 마음은 없어보인다...
SRP 먼저 어떤 말을 하는 것인지 알아보자.
abstract class Restaurant {
abstract fun buyIngredient()
abstract fun cook()
abstract fun serve()
abstract fun manage()
abstract fun advertise()
}
abstract class FoodFactory {
abstract fun buyIngredient()
abstract fun cook()
abstract fun manage()
abstract fun advertise()
}
레스토랑과 가공음식을 만드는 공장이 있다고 가정해 보자.
그리고 각각이 하는 일을 몇 가지 싹만 뽑아서 함수로 만들어 주었다.
그리고 위 추상클래스를 구현한 클래스가 다음 자료라고 가정해 보자.
class Restaurant {
fun cook() {
println("사람이 정성스럽게 만들어요")
}
...
}
class FoodFactory {
fun cook() {
println("사람이 정성스럽게 만들어요")
}
...
}
어느 날 음식을 만드는 로봇의 가격이 저렴해지고 음식의 질이 향상되었다면 음식점과 음식공장 모두 바꾸지 않을 이유가 없어질 것이다.
그때, 음식점 클래스와 음식공장 클래스는 cook() 메서드를 모두 수정해 주어야 할 것이다.
class Restaurant {
fun cook() {
println("기계가 맛있게 만들어요")
}
...
}
class FoodFactory {
fun cook() {
println("기계가 맛있게 만들어요")
}
...
}
다음과 같은 방식으로 진행해 보자.
class Restaurant(
private val chef: Chef
) {
fun cook() = chef.cook()
...
}
class FoodFactory(
private val chef: Chef
) {
fun cook() = chef.cook()
...
}
class Chef {
fun cook() {
println("사람이 정성스럽게 만들어요") /* 수정 */
}
}
위와 같이 코드를 작성하게 되면 cook method의 수정이 필요할 때 Chef 클래스의 cook method의 수정만 진행해 주면 된다.
이는 cook method를 사용하는 클래스가 많으면 많을수록 편의성은 증대될 것이다.
그래서 위의 두 클래스를 조금 더 만져보면
class Restaurant(
private val inventoryManager: InventoryManager,
private val server: Server,
private val manager: Manager,
private val chef: Chef,
) {
fun buyIngredient() = inventoryManager.buyIngredient()
fun cook() = chef.cook()
fun manage() = manager.manage()
fun advertise() = manager.advertise()
fun serve() = server.serve()
}
class FoodFactory(
private val inventoryManager: InventoryManager,
private val manager: Manager,
private val chef: Chef,
){
fun buyIngredient() = inventoryManager.buyIngredient()
fun cook() = chef.cook()
fun manage() = manager.manage()
fun advertise() = manager.advertise()
}
class InventoryManager {
fun buyIngredient() { }
}
class Server {
fun serve() { }
}
class Manager {
fun manage() { }
fun advertise() { }
}
class Chef {
fun cook() {
println("사람이 정성스럽게 만들어요")
}
}
위와 같이 작성할 수 있다.
이렇듯 음식점과 음식공장의 각 함수를 역할에 따라서 적당한 클래스를 통해 생성해 주었고, 구현부를 위임해 주었다.
위와 같은 작업을 하는 것이 SRP 원칙에 따른 설계이다.
마지막으로 음식점의 Server class를 잠깐 살펴보자.
serve는 Server가 할 것이기에 클래스를 만들어 구현을 해주었지만
음식점이 단독적으로 사용하는 함수인데 클래스를 만들어 위와 같이 작성하면
코드 길이만 늘어나는 일일 것이다.
하지만 코드가 이후 확장되어 Server 클래스의 serve method를 사용할 일이 많아질 거라고 예상한다면
반드시 해야 하는 작업일 수 있다.
이렇듯 SRP원칙에 따라서 클래스의 기능들을 조각조각 나누어 구현할 것인데 그 크기가 어느 정도인지는
코드를 작성하는 사람이 결정하는 것이라는 거다.
위의 Chef 클래스의 경우에도 대형 호텔의 경우에 대입하게 되면 한식셰프, 중식셰프, 양식셰프 등의 클래스로 추가적으로
나누어져야 할 수도 있다.
우리가 "SOLID원칙을 따르는 것이 좋다"라는 것은 어디까지나 유지, 보수, 확장의 용이성과 가독성등의 편의 증대를 위한 것이다.
원칙에 매몰되어서 작성하면 낭비적인 일이 될 수도 있다.
나머지 4개는 다음 글에 작성하도록 하겠다.