SOLID

Judy·2022년 5월 18일
0

iOS

목록 보기
9/28

SOLID

객체 지향 프로그래밍과 설계의 다섯가지 원칙을 첫 글자로 소개한 것

로버트 마틴이 정한 원칙을 마이클 페더스가 SOLID라는 이름으로 소개했다!

SOLID 원칙을 적용하게 되면 시간이 지나도 유지보수와 확장이 쉬운 시스템을 만들 수 있다.


SRP

단일 책임 원칙(Single Responsibility Principle)

클래스는 하나의 책임만 가진다.


여기서 책임이란 변경하려는 이유를 의미한다.

  • 어떤 변화에 의해 클래스를 변경하는 이유는 오직 하나여야 한다.
  • 클래스는 하나의 기능만 가진다.


예시

class Book {
	func getTitle() -> String {
    	return "책 제목"
    }
    
    func printPage() {
    	// 현재 페이지 인쇄하기
    }
}

Book 이라는 클래스에 책의 저자와 제목을 알 수 있는 메서드와 현재 페이지를 인쇄하는 메서드가 동시에 있다고 생각해보자. Book 객체를 사용하는 주체는 책을 관리하는 입장데이터를 표현하는 입장 두 가지 유형으로 나눌 수 있다. 그러면 두 가지 역할이 혼합되어 단일 책임 규칙을 위배하게 되는 것이다.

class Book {
	func getTitle() -> String {
    	return "책 제목"
    }   
}

class Print {
	    func printPage() {
    	// 현재 페이지 인쇄하기
    }
}

위와 같은 경우에는 책을 관리하는 기능과 인쇄하는 기능을 분리해서 클래스로 만들면 된다.

적용 방법

  • 클래스는 자신의 이름이 나타내는 일을 하도록 한다.
  • 클래스는 하나의 개념을 나타내도록 한다.


OCP

개방폐쇄의 원칙(Open Close Principle)

소프트웨어의 요소는 확장에는 열려 있고, 변경에는 닫혀 있어야 한다.

  • 소프트웨어의 구성요소: 컴포넌트, 클래스. 모듈, 함수 등
  • 요구사항에 변경이나 추가 사항이 발생해도 기존 구성 요소는 수정이 없도록 하고, 기존 구성 요소를 쉽게 확장해서 재사용할 수 있어야 한다.


예시

class Area {
	func calculateArea() -> Int {
    	if 도형 == 직사각형 {
        	//직사각형의 넓이 구하기
        } else if 도형 =={
        	//원의 넓이 구하기
        }
    }
}

도형의 넓이를 구하는 클래스 AreacalculateArea() 메서드가 있다면 직사각형의 넓이를 구하다가 원의 넓이도 구해야하면 직접 함수에 원일 경우를 추가해야 한다. 이는 수정에 닫혀 있지 않은 경우로 만약 삼각형의 넓이가 필요하다면 또 수정이 필요하다.

protocol Shape {
	func calculateArea() -> Int
}

class Rectangle: Shape {
	func calculateArea() -> Int {
        //직사각형의 넓이 구하기
    }
}

class Circle: Shape {
	func calculateArea() -> Int {
        //원의 넓이 구하기
    }
}

OCP를 준수하기 위해서는 Shape이라는 프로토콜에서 calculateArea() 메서드를 정의한 후 도형 별로 프로토콜을 채택하여 각자의 넓이를 구하도록 구현하면 된다.

적용 방법

  • 확장될 것과 변하지 않을 것을 엄격히 구분하기
  • 추상화를 이용하기


LSP

리스코프 치환 원칙(Liskov substitution principle)

서브 타입은 언제나 기반 타입으로 호환될 수 있어야 한다.


치환이 된다는 말은 S가 T의 하위형이라면 별도의 변경 없이 T의 자료형을 S의 객체로 교체할 수 있다는 의미이다.

파생된 클래스는 베이스의 클래스의 기능을 교체하는 것이 아니라 유지하면서 단순히 확장해야 한다.


예시

class Rectangle {
	var width: Double = 0
    var lenght: Double = 0
    var area: Double {
    	return width * lenght 
    }
}

class Square: Rectangle {
	override var width: Double {
    	didSet {
        	lenght = width
        }
    }
}

RectangleSquare가 정의되어 있다고 생각해보자.

func printArea(shape: Rectangle) {
	shape.lenght = 5
    shape.width = 2
    print(shape.area)
}

let rectangle = Rectangle()
let square = Square()

printArea(shape: rectangle)	// 10
printArea(shape: square)	// 4

SquareRectangle를 상속했을 경우 Rectangle의 방식으로 Square의 넓이를 구하면 맞지 않는다. 즉 상위 타입이 하위 타입을 대체하지 못한 것으로 LSP를 위반한 경우다.

protocol Shape {
	var area: Double { get }
}

class Rectangle: Shape {
	var width: Double = 0
    var lenght: Double = 0
    var area: Double {
    	return width * lenght 
    }
}

class Square: Shape {
	var side: Double = 0

    var area: Double {
    	return side * side 
    }
}

func printArea(shape: Shape) {
    print(shape.area)
}

이 경우에도 역시 프로토콜을 이용하면 된다. SquareRectangle 모두 Shape 프로토콜을 채택한 후 area를 구현하면 Shape을 이용해 각각의 넓이를 맞게 구할 수 있다.

적용 방법

  • 두 개체가 동일한 일을 한다면 하나의 클래스로 표현하고 이들을 구분할 수 있는 필드를 두기
  • 똑같은 동작을 제공하지만 약간씩 다르게 한다면 공통의 인터페이스(프로토콜)을 두고 이를 구현하기
  • 공통인 연산이 없다면 완전 별개인 클래스로 분리하기
  • 만약 추가적으로 동작이 필요하다면 구현 상속을 하기


ISP

인터페이스 분리 원칙(Interface segregation principle)

가능한 최소한의 인터페이스만 사용해야 한다.

하나의 일반적인 인터페이스보다는, 여러 개의 구체적인 인터페이스가 낫다는 의미로도 해석할 수 있다. SRP가 클래스의 단일책임을 강조한다면 ISP는 인터페이스의 단일책임을 강조한다.

  • 클래스의 상속을 이용하여 인터페이스를 나눌 수 있다.

  • 위임(Delegation)응 이용하여 인터페이스를 나눌 수 있다.
    : 다른 클래스의 기능을 사용해야 하지만 그 기능을 변경하고 싶지 않을 때 상속 대신 위임을 사용

  • 서로 다른 성격의 인터페이스를 명백히 분리한다.


DIP

의존관계 역전 원칙(Dependency inversion principle)

추상화에 의존해야지, 구체화에 의존하면 안된다.

의존성 주입이 이 원칙을 따르는 방법 중 하나이다. 하위 레벨 변경이 상위 레벨의 변경을 요구하지 않는다.

  • 추상을 매개로 해서 관계를 최대한 느슨하게 만드는 원칙
  • 상위와 하위 객체가 모두 동일한 추상화에 의존해야 한다.
profile
iOS Developer

0개의 댓글