SOLID Design Principles

godo·2022년 8월 15일
0

Swift - Design Patterns

목록 보기
1/24

Seperation of Concerns (SoC) ... 관심사 분리

  • 컴퓨터 프로그램을 구별된 부분으로 분리시키는 디자인 원칙으로, 각 부문은 개개의 관심사를 해결합니다.

  • 관심사란 컴퓨터 프로그램 코드에 영향을 미치는 정보의 집합, 관심사는 코드 최적화가 필요한 하드웨어의 세세한 부분만큼 포괄적이거나, 시작할 클래스의 이름처럼 구체적일 수 있습니다. SoC를 구현하는 프로그램은 모듈러 프로그램이라고 부릅니다.

  • 관심사 분리를 이용하면 프로그램의 설계, 디플로이, 이용의 일부 관점에 더 높은 정도의 자유가 생깁니다. 이 가운데 일반적인 것은 코드의 단순화 및 유지보수의 더 높은 수준의 자유입니다. 관심사가 잘 분리될 때 독립적인 개발과 업그레이드 외에도 모듈 재사용을 위한 더 높은 정도의 자유가 있습니다. 모듈이 인터페이스 뒤에서 이러한 관심사의 세세한 부분을 숨기기 때문에 자유도가 높아짐으로써 다른 부분의 세세한 사항을 모르더라도, 또 해당 부분들에 상응하는 변경을 취하지 않더라도 하나의 관심사의 코드 부분을 개선하거나 수정할 수 있게 됩니다. 또, 모듈은 각기 다른 버전의 인터페이스를 노출할 수 있으며, 이를 통해 중간의 기능 손실 없이 단편적인 방식으로 복잡한 시스템을 업그레이드하는 자유도를 높여줍니다.

  • 관심사 분리는 추상화의 일종입니다. 대부분의 추상화에서처럼 인터페이스의 추가는 필수이며 실행에 쓰이는 더 순수한 코드가 있는 것이 일반적입니다. 그러므로 잘 분리된 관심사의 여러 장점에도 불구하고 관련 실행에 따른 불이익이 있기도 합니다.

출처 : https://ko.wikipedia.org/wiki/%EA%B4%80%EC%8B%AC%EC%82%AC_%EB%B6%84%EB%A6%AC#:~:text=%EC%BB%B4%ED%93%A8%ED%84%B0%20%EA%B3%BC%ED%95%99%EC%97%90%EC%84%9C%20%EA%B4%80%EC%8B%AC%EC%82%AC%20%EB%B6%84%EB%A6%AC,%EC%9D%98%20%EA%B4%80%EC%8B%AC%EC%82%AC%EB%A5%BC%20%ED%95%B4%EA%B2%B0%ED%95%9C%EB%8B%A4.&text=%EA%B4%80%EC%8B%AC%EC%82%AC%20%EB%B6%84%EB%A6%AC%EB%A5%BC%20%EC%9D%B4%EC%9A%A9%ED%95%98%EB%A9%B4,%EC%A0%95%EB%8F%84%EC%9D%98%20%EC%9E%90%EC%9C%A0%EA%B0%80%20%EC%83%9D%EA%B8%B4%EB%8B%A4.

Single Responsibility Principle

class Journal: CustomStringConvertible {
    
    var entries = [String]()
    var count = 0
    
    func addEntry(_ text: String) -> Int {
        
        count += 1
        entries.append("\(count) : \(text)")
        return count - 1
    }
    
    func removeEntry(_ index: Int)
    {
        entries.remove(at: index)
    }
    
    var description: String
    {
        return entries.joined(separator: "\n")
    }
    
    func save(_ filename: String, _ overwrite: Bool = false)
    {
        // save to a file
        
    }
    
    func load(_ filename: String) {}
    func load(_ url: URL) {}
}

class Persistence
{
    func saveToFile(_ journal: Journal,
                    _ filenmae: String,
                    _ overwriet: Bool = false)
    {
        
    }
}


func main()
{
    let j = Journal()
    let _ = j.addEntry("I cried today")
    let bug = j.addEntry("I ate a bug")
    print(j)
    
    j.removeEntry(bug)
    print("===")
    print(j)
    
    let p = Persistence()
    let filename = "/mnt/c/ffafe"
    p.saveToFile(j, filename)
    
}

main()

위 코드에서 Journal 클래스에선 데이터를 읽고 쓰는 역할을
Persistence 클래스에선 파일을 저장하는 역할을 가지고 있는 것을 알 수 있습니다.

Open Closed Principle

// Specification

protocol Specification
{
    associatedtype T
    func isSatisfied(_ item: T) -> Bool
}

protocol Filter
{
    associatedtype T
    func filter<Spec: Specification>(_ items: [T], _ spec: Spec) -> [T]
        where Spec.T == T;
}

class AndSpecification<T,
                       SpecA: Specification,
                       SpecB: Specification> : Specification
                        where SpecA.T == SpecB.T, T == SpecA.T, T == SpecB.T
{
    let first: SpecA
    let second: SpecB
    init(_ first: SpecA, _ second: SpecB)
    {
        self.first = first
        self.second = second
    }
    
    func isSatisfied(_ item: T) -> Bool
    {
        return first.isSatisfied(item) && second.isSatisfied(item)
    }
}
                       

class BetterFilter : Filter
{
    typealias T = Product
    
    func filter<Spec>(_ items: [Product], _ spec: Spec) -> [Product] where Spec : Specification, Product == Spec.T
    {
        var result = [Product]()
        
        for i in items
        {
            if spec.isSatisfied(i)
            {
                result.append(i)
            }
        }
        
        return result
    }
}

class ColorSpecification : Specification
{
    
    typealias T = Product
    let color: Color
    init(_ color: Color)
    {
        self.color = color
    }
    
    func isSatisfied(_ item: Product) -> Bool
    {
        return item.color == color
    }
    
}

class SizeSpecification : Specification
{
    
    typealias T = Product
    let size: Size
    init(_ size: Size)
    {
        self.size = size
    }
    
    func isSatisfied(_ item: Product) -> Bool
    {
        return item.size == size
    }
    
}


let bf = BetterFilter()
print("Large blue items")
for p in bf.filter(products,
                       AndSpecification(ColorSpecification.init(.blue)
                 , SizeSpecification(.large))
                 )
{
    print(" - \(p.name) is Large and Blue")
}

Liskov Substitution Principle

class Rectangle : CustomStringConvertible
{
    var _width = 0
    var _height = 0
    
    var width: Int
    {
        get { return _width }
        set(value) { _width = value }
    }
    
    var height: Int
    {
        get { return _height }
        set(value) { _height = value }
    }
    
    init(){}
    init(_ width: Int, _ height: Int)
    {
        _width = width
        _height = height
    }
    
    var area: Int
    {
        return _width * height
    }
    
    public var description: String
    {
        return "widht : \(width), height: \(height)"
    }
    
}

class Square : Rectangle
{
    override var width: Int
    {
        get {return _width}
        set(value)
        {
            _width = value
            _height = value
        }
    }
    
    override var height: Int
    {
        get {return _height}
        set(value)
        {
            _width = value
            _height = value
        }
    }
}


func setAndMeasure(_ rc: Rectangle)
{
    rc.width = 3
    rc.height = 4
    print("Expected area to be 12 but got \(rc.area)")
}

func main()
{
    let rc = Rectangle()
    setAndMeasure(rc)
    
    let sr = Square()
    setAndMeasure(sr)
}

Inerface Segregation Principle

protocol Machine
{
    func print(d: Document)
    func scan(d: Document)
    func fax(d: Document)
    
}

이렇게 많은 기능이 있는 프로토콜을

protocol Printer
{
    func print(d: Document)
}

protocol Scanner
{
    func scan(d: Document)
}

protocol Fax
{
    func fax(d: Document)
}

이런식으로 나눠줄 수 있고

protocol MultiFunctionDevice : Printer, Scanner, Fax
{
}

이렇게 합쳐줄 수 도 있습니다.

Dependency Inversion Principle

protocol RelationshipBrowser
{
    func findAllChildrenOf(_ name: String) -> [Person]
}


class Relationships : RelationshipBrowser // low - level
{
    private var relations = [(Person, Relationship, Person)]()
    
    func addParentAndChild(_ p: Person, _ c: Person)
    {
        relations.append((p, .parent, c))
        relations.append((c, .child, p))
    }
    
    func findAllChildrenOf(_ name: String) -> [Person] {
        
        return relations
            .filter { $0.name == name && $1 == .parent && $2 === $2 }
            .map {$2}
    }
}

class Research // high - level
{
//    init(_ relationships: Relationships)
//    {
//        let relations = relationships.relations
//        for r in relations where r.0.name == "John" && r.1 == .parent
//        {
//            print("John has a child called \(r.2.name)")
//        }
//    }
    
    init(_ browser: RelationshipBrowser)
    {
        for p in browser.findAllChildrenOf("John")
        {
            print("John has a child called \(p.name)")
        }
    }
    
}

high-level 에서 low-level 에 관여할 수 없다

정리

  • SRP : 클래스는 하나의 기능을 가져야 함
  • OCP : 클래스는 확장에는 열려 있어야 하지만 변형에는 닫혀 있어야 함
  • LSP : 자식 타입을 다른 타입으로 바꿀 수 있어야 함
  • ISP : 인터페이스에 너무 많은 것을 넣지 않기, 인터페이스를 분할하기
  • DIP : high-level 모듈은 low-level 에 관여하지 않아야 함, 추상화 사용
profile
☀️☀️☀️

0개의 댓글