最近在搞代码重构,这是一个很好的学习软件规划准则、规划形式、架构规划并实践的机会,本文是以一个iOS开发人员对软件规划准则的一个归纳总结。

一、概略

软件规划准则和规划形式是紧密相关的两个概念,但它们有着不同的焦点和目的。软件规划准则主要重视怎么规划“好的”软件,强调的是架构规划方面的标准和辅导思想;而规划形式则是针对详细的问题和场景,供给精密的解决计划,这些计划包括了详细完成细节和代码结构。
软件规划准则是辅导软件开发的通用标准和辅导思想,是从更高层面上辅导软件规划的。常见的软件规划准则有SOLID准则、KISS准则、DRY准则、YAGNI准则等等。

下面来对详细的”准则”做个了解.

二、SOLID准则

  • 敞开关闭准则(Open Close Principle)
    对修正关闭,对扩展敞开。比如工厂办法形式和策略形式,工厂办法形式经过界说一个笼统的工厂类来办理产品方针的实例化,以便于在不改动原有代码的基础上新增产品方针的类型。
    简略工厂、工厂办法、笼统工厂规划形式-iOS

  • 依靠倒置准则(Dependence Inversion Principle)
    依靠于笼统,而不依靠于详细。
    依靠倒置准则中的核心思想是:高层模块不应该依靠于低层模块,而两边都应该依靠于笼统。在UITableView中,咱们可以看到UITableViewDataSourceUITableViewDelegate便是笼统,而UITableView便是依靠于这两个协议完成列表的数据源和署理。

  • 单一责任准则(Simple Responsibility Principle)
    规划的类只负责一种类型的责任,只要一个引起它改动的原因。这样可以使代码愈加明晰,避免出现过度复杂的承继联系和耦合。比如在UIViewController中,可以依据规划形式将不同类型的使命交给不同的类去完成,保证责任单一,代码也明晰易懂。

  • 接口隔离准则(interface Segregation Principle)
    用多个专门的接口,而不运用单一的总接口,客户端不应该依靠它不需求的接口。在规划时应该留意:
    1)一个类对另一个类的依靠应该树立在最小接口上。
    2)树立单一接口,不要树立庞大臃肿的接口。
    3)尽量细化接口,接口中的办法尽量少(不是越少越好,而是适度)。

  • 里氏替换准则(Liskov Substitution Principle)
    要求程序中,运用基类的方针可以被其子类的方针所替代,而不会产生任何过错或反常,运用者不需求关心完成的详细类。在iOS开发中,咱们可以运用多态的特性,让不同的子类方针经过父类来运用,然后添加代码的灵活性和扩展性。

三、迪米特规律(Law of Demeter)

也称最少知道准则,Least Knowledge Principle, LKP
指一个方针应该对其他方针坚持最少的了解,尽量下降类与类之间的耦合度。一个类应依靠于那些最直接合理的类,而不是依靠于许多其他类。主要是:

  1. 一个方针只应该调用直接朋友(即与其它方针有直接联系的方针)的办法。在方针之间树立一条明确的通讯路径可以下降耦合度,使体系更简单维护和修正。
  2. 慎重运用外观形式: 外观形式可以协助咱们下降耦合性,但是在运用时需求留意,放置太多的事务逻辑代码到外观形式中会导致方针之间相互依靠,不利于扩展和维护。
  3. 不露出任何细节:一个好的规划应该将体系的完成细节封装在类内部,不向外露出任何不必要的细节信息。这样可以下降类之间的耦合度,使体系愈加安稳和灵活。
    例如:在iOS开发中,UIView只知道与其相关联的UIViewController,而不需求知道UIViewController背面的一层层事务逻辑和数据存储的完成,以此来完成类间的解耦合。另外一个比如是KVO.

四、组成复用准则(Composite/Aggregate Reuse Principle)

用组合和聚合联系来替代承继完成代码复用。这儿的组合指的是经过将一个或多个方针(组合部分)组组成一个更大的、有着更高层次笼统的全体(组合全体),而聚合则是指在一个类中引用另一个类的实例。

五、KISS准则(Keep It Simple And Stupid)

KISS准则鼓励开发人员尽或许运用简略明了的办法,以最小化复杂度和完成相应逻辑的过错率。强调在规划软件时应力求简略,避免复杂和不必要的细节和冗余,以坚持代码的简洁、易理解、可维护和可扩展。

六、YAGNI准则(You Ain’t Gonna Need It)

YAGNI准则是一种迭代开发和极限编程中的规划哲学,它告知程序员不要在软件中添加除了当前需求之外的任何功用,避免浪费时间和精力开发无用功用,或在被证明是需求之前猜测未来的需求。

七、DRY准则(Don’t Repeat Yourself)

DRY准则要求程序员避免代码重复,避免重复造轮子,复用已有的模块和代码。这可以协助减少过错率,进步代码的可读性、可维护性和可扩展性。

八、GRASP准则(General Responsibility Assignment Software Parren)

责任分配软件形式,GRASP准则供给了一些形式和约束条件,协助程序员正确分配和挑选各个类和方针所应负责的责任,以进步代码的可读性、可维护性和可扩展性。主要有以下九个:

  • 制作者(Creator):尽量将方针的创立和初始化工作封装到一个专门的类中。
  • 控制器(Controller):控制器形式用于协谐和控制体系中的各种活动和使命。
  • 信息专家(Infomation Export):问题的解决应该由尽或许具有相关常识的方针来处理。
  • 低耦合(Low Coupling): 尽量减少不同方针之间的依靠联系,然后下降耦合。
  • 高内聚(High Cohesion): 在单个方针中将相关的特点和办法组织在一起,以进步其内聚性。
  • 间接性(Indirection): 运用一个中心方针来封装和办理不同方针之间的通讯。
  • 多态性(Polymorphism): 经过多态来处理方针或许的状况改动和状况转化,以增强程序的灵活性。
  • 受维护的改动(Protected Variations): 在或许发生改动的地方创立维护壳,以避免改动对体系的影响。详细来说,它建议在规划类或模块时,将或许受改动影响的部分封装在一些具有笼统接口的类中,经过笼统层和多态性来约束改动对体系的影响,然后进步体系的可维护性和可扩展性。
  • 纯虚拟(Pure Fabrication):创立一个虚拟的类或方针来表明某种行为或使命,并将其从任何现有的类中分离出来

十、几个实例

1. 接口隔离准则的实例

一个常见的运用场景是网络恳求,咱们可以经过封装一个网络恳求库来方便地对外供给网络恳求的功用。在完成网络恳求库时,咱们可以运用接口隔离准则,将网络恳求接口拆分红愈加细粒度的接口,如下所示:

protocol NetworkRequestProtocol {
    associatedtype ResponseDataType
    func get(url: URL, parameters: [String: Any]?,
             completion: ((Result<ResponseDataType, NetworkError>) -> Void)?)
    func post(url: URL, parameters: [String: Any]?,
              completion: ((Result<ResponseDataType, NetworkError>) -> Void)?)
}
protocol NetworkRequestConfigurableProtocol {
    func setHTTPHeaderFields(_ headers: [String: String])
    func setSerializationType(_ type: NetworkRequestSerializationType)
}

2. 间接性(Indirection)准则的实例

运用MusicProvider这个中心方针作为间接层,经过运用 MusicProvider 方针,咱们可以强制对歌曲访问进行验证,而不会直接运用 AudioPlayer 方针,然后完成 Indirection 准则.

protocol MusicProviderProtocol {
    func playSong(_ song: Song, completionHandler: @escaping (Error?) -> Void)
}
class MusicProvider: MusicProviderProtocol {
    let audioPlayer: AudioPlayerProtocol
    let accessManager: AccessManagerProtocol
    init(audioPlayer: AudioPlayerProtocol, accessManager: AccessManagerProtocol) {
        self.audioPlayer = audioPlayer
        self.accessManager = accessManager
    }
    func playSong(_ song: Song, completionHandler: @escaping (Error?) -> Void) {
        guard accessManager.hasAccess(to: song) else {
            // 提示用户登录或晋级账户以取得满足的访问权限
            completionHandler(MyErrors.insufficientAccess)
            return
        }
        audioPlayer.play(song) { error in
            completionHandler(error)
        }
    }
}

3.受维护的改动(Protected Variations)准则的实例

假设咱们正在开发一个iOS运用程序,该运用程序包括一个视频播映器功用。咱们希望可以将不同的视频播映程序集成到运用程序中,例如AVFoundation或第三方播映器SDK等。为了完成这个方针,咱们可以经过下面的方法来运用Protected Variations准则:

咱们创立一个名为VideoPlayerProtocol的协议,它界说了播映器的基本行为和接口。在详细的播映器类中,咱们将完成视频播映的详细逻辑。协议和详细类的划分允许咱们在未来从运用程序中修正详细的播映器完成,而无需改动播映器到运用程序的接口。这可认为运用程序带来极大的灵活性和可维护性。

// VideoPlayerProtocol defines the interface for the video player
protocol VideoPlayerProtocol {
    var url: URL { get set }
    func play()
}
// We implement the VideoPlayerProtocol for AVFoundation
class AVFoundationVideoPlayer: VideoPlayerProtocol {
    var url: URL
    init(url: URL) {
        self.url = url
    }
    func play() {
        // Play with AVPlayer
    }
}
// We can add other video players using the same VideoPlayerProtocol
class ThirdPartyVideoPlayer: VideoPlayerProtocol {
    var url: URL
    init(url: URL) {
        self.url = url
    }
    func play() {
        // Play with third-party player SDK
    }
}

4. 纯虚拟(Pure Fabrication)

如下,经过PersistenceManagerAuthService两个虚拟类将事务逻辑和非事务逻辑分离开来并进步代码的复用性和可维护性。

class AuthService {
  static let shared = AuthService()
  func validate(username: String, password: String, completion: @escaping (Bool) -> Void) {
    // 发送网络恳求并验证用户名和密码
    // 恳求完成后调用 completion
  }
}
class PersistenceManager {
  static let shared = PersistenceManager()
  func save(user: User) {
    // 保存用户信息
  }
  func load(completion: @escaping (User?) -> Void) {
    // 加载用户信息
    // 加载完成后调用 completion
  }
}
class User {
  var username: String
  var email: String
  var password: String
  func signIn(completion: @escaping (Bool) -> Void) {
    AuthService.shared.validate(username: username, password: password) { isValid in
      completion(isValid)
    }
  }
  func save() {
    PersistenceManager.shared.save(user: self)
  }
}