美文网首页
软件设计原则-iOS

软件设计原则-iOS

作者: Sweet丶 | 来源:发表于2023-06-09 22:43 被阅读0次

最近在搞代码重构,这是一个很好的学习软件设计原则、设计模式、架构设计并实践的机会,本文是以一个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)
  }
}

相关文章

  • 软件设计原则讲解,昭昭在目

    一、UML 图 不要觉得奇怪为什么不讲软件设计原则而说到了 UML 图,因为软件设计原则和软件设计模式中你讲到最多...

  • 设计模式之设计原则

    软件设计原则(Software design principles) 开闭原则: 定义:一...

  • SOLID 软件设计原则

    什么是软件设计原则? 软件设计原则是一组帮助我们避开不良设计的指导方针。这些设计原则是由 Robert Marti...

  • 揭秘IoC注入架构,实现RecyclerView条目点击

    依赖倒置原则(Dependency Inverse Principle)一种软件设计原则 控制反转 (Invers...

  • c++ 设计模式 - 1

    1.什么是好的软件设计?软件设计的金科玉律:复用 2.设计模式八大原则 依赖倒置原则(DIP)高层模块(稳定)不应...

  • 今日份打卡 119/368

    技术文章软件设计原则SOLID单一职责原则里氏替换原则依赖倒置原则接口隔离原则迪米特法则开放封闭原则

  • 软件设计7大原则

    软件设计7大原则 开闭原则 依赖倒置原则 单一职责原则 接口隔离原则 迪米特法则(最少知道原则) 里氏替换原则 合...

  • 架构师学习路线图

    内功心法 设计模式 软件设计原则 软件设计模式创建型模式Factory 工厂模式Singleton 单例模式Pro...

  • 软件设计原则

    前言 思想 原则 做某件事或解决某个问提或在某个领域里不能离开的禁止性规定。 模式 针对类似问题的通用解决指导方法...

  • 软件设计原则

    翻译: 疯狂的技术宅来源: Programmer Gate原文标题: Software design princi...

网友评论

      本文标题:软件设计原则-iOS

      本文链接:https://www.haomeiwen.com/subject/dlamultx.html