iOS 개발의 SOLID 원칙

34056 단어 solidengineeriosswift

iOS 개발의 SOLID 원칙



😩 좋습니다. 아마 이 용어를 이미 들었을 것입니다. 하지만 실제로는 무슨 뜻인가요? 언제 사용합니까? 어떻게 사용합니까? 마지막으로 배우기 위해 계속 읽으십시오





우선, SOLID는 소프트웨어 디자인을 보다 이해하기 쉽고 유연하며 유지 관리하기 쉽게 만들기 위한 다섯 가지 디자인 원칙의 니모닉 약어입니다.



모든 튜토리얼에서 나는 SOLID 원칙을 행동으로 보여주고 SOLID를 따르지 않는 코드를 보여줄 것입니다.



자, 가자 😁




S - 단일 책임 원칙



클래스는 변경(존재)해야 하는 한 가지 이유가 있어야 합니다.



신청 팁


  • 모든 엔터티( 클래스/함수 )에 대해 스스로에게 물어보십시오. 이 엔터티는 한 가지 이상의 작업을 수행합니까?
  • 해당 단어를 사용해서는 안 되며 엔티티에 대한 책임을 이야기해야 할 때

  • 💢 단일 책임 원칙이 적용되지 않음




    class Handler_NOT_SOLID {
    
        func handle() { 
    
            let data = requestDataToAPI()
            guard let dataReceive = data else { return }
            let array = parse(data: dataReceive)
            saveToDB(array: array)
    
        }
    
        private func requestDataToAPI() -> Data?{
            // send API request and wait the response
            return nil
        }
    
        private func parse(data:Data)->[String]?{
            // parse the data and create an array
            return nil
        }
    
        private func saveToDB(array:[String]?){
            // save array in a database
        }
    }
    


    당신은 볼 수 있습니까?

    Handler_NOT_SOLID 클래스에는 몇 가지 책임이 있습니다.


  • API에 요청 보내기
  • 데이터 수신으로 어레이 생성
  • 데이터베이스에 어레이 저장

  • 👍 단일 책임 원칙 적용




    class Handler_SOLID {
    
        let apiHandler: APIHandler
        let parseHandler: ParseHandler
        let dbHandler: DBHandler
    
        init(apiHandler: APIHandler, parseHandler: ParseHandler, dbHandler: DBHandler) {
            self.apiHandler = apiHandler
            self.parseHandler = parseHandler
            self.dbHandler = dbHandler
        }
    }
    
    
    class APIHandler {
    
        func requestDataToAPI() -> Data?{
            // send API request and wait the response
            return nil
        }
    }
    
    class ParseHandler {
        func parse(data:Data) -> [String]?{
            // parse the data and create an array
            return nil
        }
    }
    
    class DBHandler {
        func saveToDB(array:[String]?){
            // save array in a database
        }
    }
    
    


    이제 각 엔터티는 하나의 책임만 가집니다.


    O - 개방/폐쇄 원리



    소프트웨어 엔터티는 확장에는 열려 있어야 하지만 수정에는 닫혀 있어야 합니다.



    신청 팁


  • 새 동작이 추가될 때마다 클래스를 수정하려는 경우 뭔가 잘못된 것입니다
  • .
  • If/else/switch 문을 사용하여 동작을 수정하지 않음

  • 💢 NOT Open/Closed 원칙 적용




    class Vehicles_NOT_SOLID {
    
        func printData() {
            let cars = [
                Car_NOT_SOLID(name: "Batmobile", color: "Black"),
                Car_NOT_SOLID(name: "SuperCar", color: "Gold"),
                Car_NOT_SOLID(name: "FamilyCar", color: "Grey")
            ]
    
            cars.forEach { car in
                print(car.printDetails())
            }
    
            let buses = [
                Bus_NOT_SOLID(type: "School bus"),
                Bus_NOT_SOLID(type: "Minibus"),
                Bus_NOT_SOLID(type: "Minicoach")
            ]
    
            buses.forEach { bus in
                print(bus.printDetails())
            }
        }
    
    }
    
    class  Car_NOT_SOLID {
        let name:String
        let color:String
    
        init(name: String, color: String) {
            self.name = name
            self.color = color
        }
    
        func printDetails() -> String {
            return "name : \(name) color :\(color)"
        }
    
    }
    
    
    class Bus_NOT_SOLID {
        let type:String
    
        init(type: String) {
            self.type = type
        }
    
        func printDetails() -> String { 
            return "bus type : \(type)"
        }
    }
    
    


    당신은 볼 수 있습니까?

    printData가 다른 유형의 객체를 수신하면 더 많은 규칙을 추가해야 합니다.



    새 클래스의 세부 정보도 인쇄할 수 있는 가능성을 추가하려면 새 클래스를 기록할 때마다 printData 구현을 변경해야 합니다.

    👍 개방/폐쇄 원칙 적용




    protocol Printable {
        func printDetails() -> String
    }
    
    class Vehicles_SOLID {
    
        func printData() {
            let cars:[Printable] = [
                Car_SOLID(name: "Batmobile", color: "Black"),
                Car_SOLID(name: "SuperCar", color: "Gold"),
                Car_SOLID(name: "FamilyCar", color: "Grey"),
    
                Bus_SOLID(type: "School bus"),
                Bus_SOLID(type: "Minibus"),
                Bus_SOLID(type: "Minicoach")
            ]
    
            cars.forEach { car in
                print(car.printDetails())
            }
        }
    }
    
    class Car_SOLID:Printable {
        let name: String
        let color: String
    
        init(name: String, color: String) {
            self.name = name
            self.color = color
        }
        func printDetails() -> String {
            return "name : \(name) color :\(color)"
        }
    
    }
    
    class Bus_SOLID: Printable {
        let type: String
    
        init(type: String) {
            self.type = type
        }
    
        func printDetails() -> String {
            return "bus type : \(type)"
        }
    }
    


    printData의 동작을 변경할 필요가 없습니다. printData와 클래스 사이에 레이어를 만들기만 하면 됩니다.


    L - Liskov 대체 원리



    개체는 해당 프로그램의 정확성을 변경하지 않고 하위 유형의 인스턴스로 교체할 수 있어야 합니다.



    신청 팁


  • 하나의 모놀리식 인터페이스 대신 구현자가 수행해야 하는 작업에 따라 인터페이스를 분할합니다
  • .
  • 소비자가 많은 권한을 가지지 않도록 합니다
  • .

    💢 NOT Liskov 대체 원칙 적용




    class Rectangle_NOT_SOLID {
        var width: Double = 0
        var height: Double = 0
    
        var area: Double {
            return width * height
        }
    
    
    }
    
    class Square_NOT_SOLID: Rectangle_NOT_SOLID {
        override var width: Double {
            didSet {
                height = width
            }
        }
    }
    
    // MARK: - Implementations 
     func printArea(of rectangle: Rectangle_NOT_SOLID) {
         rectangle.width = 10
         rectangle.height = 4
    
         print(rectangle.area)
     }
    
     let rect = Rectangle_NOT_SOLID()
     printArea(of: rect) // 40
     let square = Square_NOT_SOLID()
     printArea(of: square ) // 40
    
    


    printArea(of rectangle:Rectangle_NOT_SOLID)가 각 클래스의 특정 값을 반환하는 대신 다른 유형으로 동일한 결과를 반환하는지 확인합니다.

    👍 리스코프 대체 원칙 적용




    protocol Polygon {
        var area :Double { get }
    }
    
    class Rectangle_SOLID: Polygon {
    
        let width:Double
        let height:Double
    
        init(width: Double, height: Double) {
            self.width = width
            self.height = height
        }
    
        var area: Double {
            return width * height
        }
    
    }
    
    class Square_SOLID: Polygon {
        let side:Double
    
        init(side: Double) {
            self.side = side
        }
    
        var area: Double {
            return pow(side, 2)
        }
    }
    
    /// MARK: - Implementations  
    
     func printArea(of polygon:Polygon){
         print(polygon.area)
     }
    
     let rect = Rectangle_SOLID(width: 10, height: 40)
     printArea(of: rect) // 400.0
     let square = Square_SOLID(side: 10)
     printArea(of: square) // 100.0
    
    



    I - 인터페이스 분리 원리



    (M)모든 클라이언트별 인터페이스가 하나의 범용 인터페이스보다 낫습니다.



    신청 팁


  • 하나의 모놀리식 인터페이스 대신 구현자가 수행해야 하는 작업에 따라 인터페이스를 분할합니다
  • .
  • 소비자가 많은 권한을 가지지 않도록 합니다
  • .

    💢 NOT 인터페이스 분리 원칙 적용




    //MARK:- Fat Interface (Protocol)
    protocol GestureProtocol {
        func didTap()
        func didLongPress()
        func didSwipe()
    }
    
    class RichButton_NOT_SOLID: GestureProtocol {
    
        func didTap() {
            print("tap button")
        }
    
        func didLongPress() {
            print("long press")
        }
    
        func didSwipe() {
            print("swipe")
        }
    
    }
    
    class PoorButton_NOT_SOLID: GestureProtocol {
        func didTap() {
            print("tap")
        }
    
        func didLongPress() {}
    
        func didSwipe() {}
    
    }
    


    PoorButton_NOT_SOLID 클래스에 사용할 수 없는 메서드가 있는지 확인합니다.

    👍 인터페이스 분리 원칙 적용




    protocol TapGesture {
        func didTap()
    }
    
    protocol LongPressGesture {
        func didLongPress()
    }
    
    protocol SwipeGesture {
        func didSwipe()
    }
    
    class RichButton_SOLID: TapGesture, LongPressGesture, SwipeGesture{
    
        func didTap() {
            print("tap button")
        }
    
        func didLongPress() {
            print("long press")
        }
    
        func didSwipe() {
            print("swipe")
        }
    
    
    }
    
    
    class PoorButton_SOLID: TapGesture {
        func didTap() {
              print("tap button")
          }
    }
    
    


    이제 불필요한 메서드를 모두 제거합니다.


    D - 종속성 역전 원칙



    고수준 모듈은 저수준 모듈에 의존해서는 안 됩니다. 둘 다 추상화에 의존해야 합니다.



    신청 팁


  • 우리는 이것에 익숙합니다: 높은 수준 -> (사용) 낮은 수준
  • 높은 수준 -> (예상) 인터페이스 <- ( 이행 ) 낮은 수준

  • 💢 종속성 반전 원칙이 적용되지 않음




    class SaveData_NOT_SOLID {
    
        let filesSystemManager = FilesSystemManager_NOT_SOLID()
    
        func handle(data:String){
            filesSystemManager.save(data: data)
        }
    }
    
    class FilesSystemManager_NOT_SOLID {
    
        func save(data:String){
            // save data
        }
    }
    


    이것으로 데이터를 저장하는 방법이 있습니다. 데이터베이스를 사용하려면?

    👍 종속성 역전 원칙 적용




    
    protocol Storage {
        func save(data:Any)
    }
    
    class SaveData_SOLID {
        let storage:Storage
    
        init(storage: Storage) {
            self.storage = storage
        }
    
        func handle(data: Any){
            self.storage.save(data: data)
        }
    }
    
    class FilesSystemManager_SOLID: Storage {
        func save(data: Any) {}
    }
    
    class MySQLDataBase: Storage {
        func save(data: Any) {}
    }
    


    이제 모든 저장 방법을 사용할 수 있습니다.

    👊 읽어주셔서 감사합니다!! SOLID에 대한 모든 원칙과 더 나은 소프트웨어를 구축하기 위해 매일 적용하는 방법을 이해하셨기를 바랍니다. 의심스러운 점이 있으면 아래에 의견을 남겨 주시면 도와 드리겠습니다.

    Github Project

    좋은 웹페이지 즐겨찾기