SOLID 란?

나다·2023년 4월 18일
0
  • 빠른 개발에 목적이 없음
  • 유지 보수에 목적이 있음

하지만

  • 빠른 개발이란 유지 보수를 적게 하는 개발
  • 읽기 쉬운 코드는 되지 않지만, 구조적으로 안정적인 코드

Design Pattern

  • SOLID 를 지키기위해 만든 Pattern (아주 정확한 이유가 있음)
  • 따라서 두루뭉실하게 아는 것 보다는 구체적으로 알아야 함

SRP (Single Responsibility Principle)

  • 소프트웨어의 변경의 이유가 단 하나여야 함

함수 수준

  • 함수가 변하는 이유는 하나여야 함
  • 함수가 변하는 이유가 여러 가지 라는 것 = 함수가 하는 일이 여러 가지라는 것

# process 는 순서대로 함수를 호출하는 것이 목적
# 따라서 함수를 여러 개를 사용한다고 해서 SRP 를 지키지 않는 것은 아니다
def process():
	step1()
    step2()
    step3()

  • 모든 method 를 잘게 쪼개는 것이 좋을까?
  • 얼마나 쪼개는 지는 정의되어 있지 않음
  • 알잘딱깔센으로 쪼개야 함

module 수준

  • 함수는 정의가 명확 (definition 으로 시작)
  • 근데 모듈은 정의가 불명확
  • 따라서 module 수준을 지금은 class 수준으로 이야기할 예정
  • 하나의 모듈은 오직 하나의 사용자 또는 이해관계자에 대해서만 책임져야 함

[풀이]

  • Class 가 변하는 목적은 오직 하나여야 함

  • CFO, COO, CTO Class = Actor
  • 만약 Employee 안의 함수를 CFO 가 calculatePay, COO 는 reportHours 이렇게 각각 다르게 쓰면
  • CFO 가 calculatePay 를 변경하면, 하나의 목적이 아닌 다중 목적으로 인해서 Employee 함수 변경

  • Employee class 는 하나의 목적을 가지고 만듬
  • 근데 여러 목적의 함수가 들어가면 하나의 목적이 아님
  • Utility class 를 만들지 말아라..

해결책

  • 내가 하나의 목적으로 Class 를 수정하려고 들어갔는데
  • 다른 목적도 알아야 한다면 잘못된것!

SRP 를 시키면 응집성 (coherence) 이 늘어남

+) Coupling : A가 변할때 B도 변해야 논리적으로 맞을 때
+)

  • 논리적 응집 : 코드 응집성
  • 물리적 응집 : file tree 같은 것

OCP (Open-Closed Principle)

  • 개방 폐쇄 원칙
  • 확장에는 열려 있고, 변경에는 닫혀있어야 함
  • 원래 코드를 건들이지 않고, 새로운 코드를 작성해서 확장할 수 있어야 함

함수 수준

  • 불가능 함
  • 기존에 있던 코드를 건들이지 않고 확장 불가능
def a():
	return 100; # 이걸 1000 으로 고치면?

module 수준

[상황]

  • 재무재표를 보여주는 페이지 있음 + 음수는 빨간색으로 보여줌
  • 요구 사항 : 음수는 괄호로 감싸야 함

아키텍처가 훌륭하다면 변경되는 코드는 없어야 함

  • 보고서를 프린터로 출력하는 모듈만 추가하면 됨

  • WebView 는 Screen Presenter 를 사용하고 있음
  • Screen Presenter 가 변하면 Web View 는 code 를 변경해야 함

OCP 는 과연 지켜질 수 있는 걸까?

  • Class 는 Class 끼리 서로 사용하고 있는데..?
  • 우리는 라이브러리도 사용하고 있는데..?
  • 의존성 을 가지면 변경할 수 밖에 없음
  • 따라서 OCP는 이론적으로는 가능하지만 실제로는 불가능함

모든 것에 적용하자는 것이 아님 -> 내가 지켜야 할 곳에 적용하자

  • 저 흐름도에서 보면 Financial Repor Interactor 는 의존성이 없기 때문에 OCP 를 사용할 수 있음
  • OCP 는 모든 곳에 적용 할 수 없음
  • 가장 높은 곳에 있어야 함
  • 중요도 순으로 OCP 적용 수준을 정해야 함

LSP (Liskov Substitution Principle)

  • 자식 객체가 부모 객체를 대신할 수 있어야 함

대체할 수 있다란?

  • A가 하던 기능을 B도 할 수 있어야 함
  • A.caculate() 를 쓰려면 B.calculate() 기능이 있어야 함
  • 대체 라고 했지 동일 하다고는 하지 않음
  • A 처럼 B 도 Calculate 기능이 있으면 되지, 결과가 동일할 필요는 없음

다형성

자식 객체가 부모 객체를?

[다형성 구현 방법]

  1. interface 구현 class
  2. 상속 : 자식 클래스도, 부모클래스도 동일한 함수가 있기 때문에 똑같은 메시지에 대해서 서로 다른 응답이 가능
  • LSP : interface 가 아닌 상속 으로 구현

LSP 가 나온 목적

  • Type System 한정으로 추상화를 어떻게 할지 적어놓은 규칙
class Person():
	def eat():
    	print('eat')
        
class Student(Person):
	def eat(): # Student 로 Person 의 Class 를 대체할 수 없음
    	pass

ISP (Interface Seperation Principle)

  • interface 를 분리해라
  • 분리를 어떻게 해야할까?

interface Warrier{
	def attack(){}
    def defense(){}
    def run(){}
    def walk(){}
}

class Student(Warrier){ # 학생인데 전쟁 하면 안대여..
	def attack(){}
    def defense(){}
    def run(){} # 실제로 학생이 필요한 것
    def walk(){} # 실제로 학생이 필요한 것
}
  • 따라서 분리해라
  • 내가 쓰지 않을 것에 의존하지 말아라
interface Warrier{
	def attack(){}
    def defense(){}
    def run(){}
    def walk(){}
}

interface Person{
    def run(){}
    def walk(){}
}

class Student(Person){
    def run(){}
    def walk(){}
}

의존성을 지키기 위한 도구

DI (Dendency Injection)

  • 의존성 역전의 원칙

변화하기 쉬운 것에 의존하지 말아라

  • import, include 등에서 오직 interface, abstract 선언만 있어야 하고 구체적인 대상은 오면 안됨
  • OS, Platform 과 같이 안정성이 보장된 환경에서는 허용

  • 즉 변화하기 쉬운 구체 클래스를 쓰지 말고
  • 잘 안 변화하는 interface, abstract 를 사용해라

완벽하게 절대 지킬 수 없음

class NiceService():
	Nice()
    
class TossService():
	Toss()

class KakaoService():
	Kakao()
    
class Nice()
class Toss()
class Kakao()

이렇게 하지 말고

class NiceService():
	PGFactory().create('nice')

class PGFactory():
	def create(pg_type):
    	if pg_type == 'nice':
        	return Nice()
            
class Nice()
class Toss()
class Kakao()

이렇게 하면 import 가 통일됨

객체 생성 방식을 강하게 제약

profile
매일 한걸음씩만 더 성장하기 위해 노력하고 있습니다.

0개의 댓글