设计模式-软件设计的7个原则

设计模式-软件设计的7个原则

概述

在软件开发时为了提高软件系统的可维护性和可复用性,增加软件的可扩展性和灵活性,通常要遵守一定的设计原则:

  1. 开闭原则
  2. 里式替换原则
  3. 依赖倒置原则
  4. 单一职责原则
  5. 接口隔离原则
  6. 迪米特原则
  7. 合成复用原则

1. 开闭原则

软件实体应当对扩展开放,对修改关闭。
开闭原则是软件设计的终极目标,对扩展开放可以使软件具有一定的灵活性,同时对修改关闭又可以保证软件的稳定性。使用开闭原则设计的软件有如下优势:

  • 测试方便。由于开闭原则对修改关闭,因此软件实体是拥有稳定性的,测试时只需要对扩展代码进行测试即可;
  • 更好地提高代码复用性。开闭原则通常采用抽象接口的方式来组织代码结构,抽象的编程本身就是对代码的复用性提高有很大的帮助;
  • 提高软件的维护性和扩展性。由于开闭原则对扩展开放,因此当软件需要升级时,可以很容易地通过扩展来实现新功能,开发效率更高,代码也更易于维护。

在面向对象开发中,实现开闭原则可以通过继承父类和实现接口两种方式。
在开闭原则中,一个类只应该因为错误而修改,新加入的功能都不应该修改原始代码。

重构前的代码

enum Color: String {
    case unknown
    case black
    case white
    case gray
    case blue
    case red
}
class Style {
    var backgroudColor = Color.black
    var textColor = Color.white
    func apply() {
        print("皮肤 - 背景色: \(self.backgroudColor), 文字颜色: \(self.textColor)")
    }
}
let baseStyle = Style()
baseStyle.apply()

此时,如果有一个新需求增加一个背景色为灰色,文字颜色为蓝并且按钮颜色为红的主题,如何修改?并且需要遵守开闭原则

继承

class Custom1Style: Style {
    var buttonColor = Color.red
    override init() {
        super.init()
        backgroudColor = .gray
        textColor = .blue
    }
    override func apply() {
        print("皮肤 - 背景色: \(self.backgroudColor), 文字颜色: \(self.textColor), 按钮颜色: \(self.buttonColor)")
    }
}
let custom1Style = Custom1Style()
custom1Style.apply()

通过继承方式实现开闭原则并不彻底,通过接口可以更好的实现开闭原则。

接口

protocol StyleInterface {
    var backgroudColor: Color { get }
    var textColor: Color { get }
    var buttonColor: Color { get }
    func apply()
}
extension StyleInterface {
    var buttonColor: Color {
        get {
            return .unknown
        }
    }
}
class BaseStyle: StyleInterface {
    var backgroudColor: Color = .black
    var textColor: Color = .white
    func apply() {
        print("皮肤 - 背景色: \(self.backgroudColor), 文字颜色: \(self.textColor)")
    }
}
class Custom2Style: StyleInterface {
    var backgroudColor: Color = .gray
    var textColor: Color = .blue
    var buttonColor: Color = .red
    func apply() {
        print("皮肤 - 背景色: \(self.backgroudColor), 文字颜色: \(self.textColor), 按钮颜色: \(self.buttonColor)")
    }
}
let baseStyle2 = BaseStyle()
let custom2Style = Custom2Style()
baseStyle2.apply()
custom2Style.apply()

StyleInterface 协议定义了与主题相关的属性和方法,方法需要扩展多个主题时,需要对接口进行不同的实现即可。

2. 里式替换原则

继承必须保证超类所拥有的性质在子类中依然成立。即:在进行类的继承时,要保证子类不对父类的属性或方法进行重写,只是扩展父类的功能。
如果在设计时发现子类不得不重写父类的方法,则表明类的组织结构有问题,需要重新设计类的继承关系,比如将被重写的方法从父类抽离,仅在需要的子类声明。

3. 单一职责原则

一个类只应该承担一项责任,在实际设计中,可以以是否只有一个引起类变化的原因作为准则如果不止一个原因会引起类的变化,则需要对类重新进行拆分。
如果一个类或对象承担了太多的责任,则其中一个责任的变化可以带来对其他责任的影响,且不利于代码的复用性,容易造成代码的冗余,遵守单一职责设计的程序有以下几个特点:

  • 降低类的复杂度,一个类承担单一的职责,逻辑清晰,提高内聚,降低耦合
  • 提高代码可读性和可复用性
  • 增强代码可维护性和可扩展性
  • 类的变更是必然的,功能的增加必然会产生类的变更,单一职责可以使变更带来的影响最小。

4. 接口隔离原则

将庞大的接口定义拆分为更小的和更具体的接口,其“隔离”的主要是指对接口依赖的隔离。例如 UITableViewUITableViewDataSourceUITableViewDelegate。定义的各个接口各司其职,尽量少耦合其他业务逻辑。

5. 依赖倒置原则

高层模块不应该依赖底层模块,两者都应该依赖其抽象;抽象不应该依赖细节,细节应该依赖抽象。与 开闭原则 的核心思路相同,都是要尽量减少对已有代码的修改,同时又易于进行扩展。优势:

  • 由于都对接口进行依赖,减少了类之间的耦合
  • 封闭了对类实现的修改,增强了程序的稳定性
  • 核心是面向接口开发,减少了并行开发的依赖于风险
  • 提高代码可读性和可维护性

重构前的代码:
如下代码即上层依赖下层:

class FoodStore {
    func sell(count: Int) {
        print("食品商店卖了\(count)食物")
    }
}
class Customer {
    func buy(store: FoodStore, count: Int) {
        print("购物--")
        store.sell(count: count)
    }
}
let customer = Customer()
customer.buy(store: FoodStore(), count: 4)

当有新的商店出现时就需要更改上层的 Customer 类。
使用依赖倒置的原则进行重构,使 Customer 只对抽象的接口进行依赖。

protocol Store {
    func sell(count: Int)
}
class FoodStore: Store {
    func sell(count: Int) {
        print("食品商店卖了\(count)食物")
    }
}
class ClothStore: Store {
    func sell(count: Int) {
        print("服装商店卖了\(count)服装")
    }
}
class Customer {
    func buy(store: Store, count: Int) {
        print("购物--")
        store.sell(count: count)
    }
}
let customer = Customer()
customer.buy(store: FoodStore(), count: 4)
customer.buy(store: ClothStore(), count: 2)

重构后的 Customer 不再依赖具体的 Store ,扩展也不需要更改其内部实现。

6. 迪米特原则

又叫 “最小知识原则”。核心为一个类或对象尽可能少地与其他实体发生交互作用。通常,我们不会对单独的类使用迪米特原则,这样做的解耦效果并不明显,但是如果是模块之间的交互通过一个中介类来统一处理,那就可以大大减少模块间的耦合程度,例如在iOS组件化开发中的路由器,可以将模块之间的耦合通过路由进行隔离,降低模块间的耦合。

7. 合成复用原则

在设计类的复用时,要尽量先使用组合或聚合的方式设计,尽量少使用继承。合成复用原则通过组合和聚合的方式实现复用,实现上通常使用属性、参数的方式引入其他实体进行通信。
重构前的代码:

class Teacher {
    var name: String
    init(_ name: String) {
        self.name = name
    }
    func teach() {
        print("讲课")
    }
}
class MathTeacher: Teacher {
    override func teach() {
        print("\(name)讲数学课")
    }
}
class EnglishTeacher: Teacher {
    override func teach() {
        print("\(name)讲英语课")
    }
}
let james = MathTeacher("james")
james.teach()
let davis = EnglishTeacher("davis")
davis.teach()

根据合成复用原则,不使用继承,把学科封装为Teacher的一个属性。

class Suject {
    var name: String
    init(_ name: String) {
        self.name = name
    }
}
class Teacher {
    var name: String
    var subject: Suject
    init(_ name: String, subject: String) {
        self.name = name
        self.subject = Suject(subject)
    }
    func teach() {
        print("\(name)讲\(subject.name)课")
    }
}
let james = Teacher("james", subject: "数学")
james.teach()
let davis = Teacher("davis", subject: "英语")
davis.teach()

总结

  • 开闭原则是核心,在设计软件时保持扩展的开放性和修改的封闭性
  • 里式替换原则要求在继承时不要破坏父类的实现
  • 单一职责原则要求类的功能要单一
  • 接口隔离原则要求接口的设计要精简
  • 依赖倒置原则要求面向抽象编程,即面向接口编程
  • 迪米特原则提供一种降低系统耦合性的方式
  • 合成复用原则要求组织类的关系时谨慎使用继承
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 195,783评论 5 462
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 82,360评论 2 373
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 142,942评论 0 325
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,507评论 1 267
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,324评论 5 358
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,299评论 1 273
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,685评论 3 386
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,358评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,652评论 1 293
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,704评论 2 312
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,465评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,318评论 3 313
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,711评论 3 299
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,991评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,265评论 1 251
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,661评论 2 342
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,864评论 2 335

推荐阅读更多精彩内容