设计模式-结构型
Posted 极客学伟
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了设计模式-结构型相关的知识,希望对你有一定的参考价值。
设计模式-结构型
结构型设计模式包含:代理模式、适配器模式、桥接模式、装饰模式、外观设计模式、享元模式、组合模式
代理模式
核心是在具体的功能类与使用者之间建立一个中介类作为代理,使用者通过代理对象对真实的功能类进行访问。 在ios开发中,代理设计模式非常有用,在UIKit框架中,UITableView
和 UITextView
等组件的渲染和交互都采用了代理设计模式。
以病人预约看病的软件设计举例,核心功能类只有两个医生类和病人类,病人看病前首先预约,预约完成后问诊,医生陈述病情,然后开药。整个系统中有些行为既不属于病人类也不属于医生类,如医生的预约和问诊过程的控制等,这时就需要一个代理类代理医生处理这些行为。
重构后
class Patient
func describeCondition() -> String
let describe = "描述病情"
print(describe)
return describe
class Doctor
func writPrescription(condition: String) -> String
let prescription = "依据病情: \\(condition), 开的处方"
print(prescription)
return prescription
class DoctorProxy
var patient: Patient
init(patient: Patient)
self.patient = patient
func seeDoctor()
// 预约医生
let doctor = reservation()
// 病人描述病情
let condition = self.patient.describeCondition()
// 医生开处方
doctor.writPrescription(condition: condition)
func reservation() -> Doctor
let doctor = Doctor()
print("预约医生")
return doctor
let patient = Patient()
let doctorProxy = DoctorProxy(patient: patient)
doctorProxy.seeDoctor()
其中,病人并没有和医生进行直接交互,而是通过中间的代理类 DoctorProxy
。实际开发中,使用代理设计模式可以使具体的功能类的聚合性更强,并可以在某些功能的执行前后进行额外的准备工作和善后工作。
适配器模式
适配器模式并不是软件设计中的最佳实践,其主要为了解决软件开发过程中新旧模块不兼容的问题。其定义:将一个类的接口转换成使用者期望的另外接口,使得原本接口不兼容的类可以一起工作。
当数据模型版本升级时,可以使用适配器模式兼容旧的数据模型
重构后
class User
var name: String
var age: Int
class UserV2
var nickName: String
var age: Int
var address: String
class UserAdapter
static func toUserV2(user: User) -> UserV2
return UserV2(nickName: user.name, age: user.age, address: "")
let user = User(name: "学伟", age: 18)
let userV2 = UserAdapter.toUserV2(user: user)
print(userV2)
实际开发中,由于数据模型升级造成的代码不兼容问题会经常遇到,当项目过于庞大时,如果贸然修改以往的旧代码,会有很大的工作量,同时也会伴随很大的风险,使用适配器模式就是一种比较适合的折中选择。
桥接模式
桥接模式是合成复用原则的一种应用,其核心是将抽象与实现分离,用组合来代替继承关系,从而给类更多的扩展性,降低类之间的耦合度。 实际开发中,当某个类具有多维度的属性时,在组织类的结构时,使用桥接模式十分适合。 例如:汽车从功能上分为轿车和卡车,颜色上又分为黑色白色。在设计时有两种设计方案:一种是创建轿车和卡车的类,每个类包含颜色属性:
enum Color
case red
case green
class Car
var color: Color
class Saloon: Car
print("我是轿车")
class Truck: Car
print("我是卡车")
另外一种设计方案可以根据桥接模式,根据实际需要对功能和颜色进行组合。
重构后
enum Color
case red
case green
enum CarType
case saloon
case truck
var name: String
switch self
case .saloon:
return "轿车"
case .truck:
return "卡车"
protocol CarProtocol
var color: Color get
var carType: CarType get
func log()
extension CarProtocol
func log()
print("我是" + carType.name)
class Car: CarProtocol
var color: Color
var carType: CarType
init(color: Color, carType: CarType)
self.color = color
self.carType = carType
let car = Car(color: .red, carType: .saloon)
car.log()
通过组合颜色和类型两个枚举来构建汽车对象,避免了因继承带来的耦合问题。
装饰模式
在不改变对象结构的情况下,为该对象增加一些功能。 类比现实生活中的:手机壳、壁画...
以为墙添加贴纸的逻辑设计为例:
重构后
protocol WallProtocol
func printInfo()
class Wall: WallProtocol
func printInfo()
print("墙面")
class StickerDecorator: WallProtocol
var wall: Wall
init(wall: Wall)
self.wall = wall
func printInfo()
print("贴纸装饰")
self.wall.printInfo()
let wall = Wall()
let stickerDecorator = StickerDecorator(wall: wall)
stickerDecorator.printInfo()
其中 StickerDecorator
即装饰器,也需要完整的实现功能类所实现的接口,这样才能不会改变被装饰对象的原始行为。 使用装饰模式可以理解成:为对象的行为进行扩展,只是相比较于继承,装饰模式更加灵活、类之间的耦合度也更低。同时,装饰模式可能由于过度设计而增加过多装饰器类,使系统复杂性变高。
外观设计模式
在软件设计中,当一个系统的功能越来越强时,子模块会越来越多,应用端对系统的访问也会越来越复杂。这时可以通过提供一个外观类来统一处理这些交互,降低应用端使用的复杂性。 以客户购买商品流程的设计为例:
struct User
var name: String
struct Goods
static func choseGoods(user: User)
print("\\(user.name)选择商品")
struct Cashier
static func pay(user: User)
print("\\(user.name)付款")
struct Package
static func packing(user: User)
print("\\(user.name)打包")
let user = User(name: "学伟")
Goods.choseGoods(user: user)
Cashier.pay(user: user)
Package.packing(user: user)
User
需要完成一个购物流程需要同时与 Goods
、Cashier
、Package
三个类进行交互。当每个模块都变得越来越复杂时,代码的扩展和维护将变得十分困难。 对于这样的场景,可以定义一个外观类来统一处理用户的购物逻辑。
重构后
...
struct Store
static func shop(user: User)
Goods.choseGoods(user: user)
Cashier.pay(user: user)
Package.packing(user: user)
let user = User(name: "学伟")
Store.shop(user: user)
其中,Store
起到外观的作用,顾客只需要与 Store
一个类进行交互即可,
享元模式
运用共享技术实现大量细粒度对象的复用,避免大量重复对象造成系统的资源开销。 在享元模式中,需要根据共享性将对象中的数据拆分成内部状态和外部状态,之后将内部状态封装成享元对象用户共享。享元模式会增加系统的复杂度,对于不会产生大量重复对象的系统并不适用。
以黑白棋设计为例:
struct Place
var x: Int
var y: Int
enum Color
case White
case Black
class ChessPiece
var place: Place
var color: Color
var radius: Double
init(place: Place, color: Color, radius: Double)
self.place = place
self.color = color
self.radius = radius
一个棋子除了位置不同外,颜色和半径对于大部分棋子来说是相同的,这种场景下,place 就是 外部状态,color与radius为内部状态,可以使用享元模式重构
重构后
struct Place
var x: Int
var y: Int
enum Color
case White
case Black
class ChessPieceFlyweight
var color: Color
var radius: Double
init(color: Color, radius: Double)
self.color = color
self.radius = radius
class ChessPieceFlyweightFactory
static let white = ChessPieceFlyweight(color: .White, radius: 16.0)
static let black = ChessPieceFlyweight(color: .Black, radius: 16.0)
static func getChessPieceFlyweight(color: Color) -> ChessPieceFlyweight
switch color
case .White:
return white
case .Black:
return black
class ChessPiece
var place: Place
var chessPieceFlyweight: ChessPieceFlyweight
init(place: Place, color: Color)
self.place = place
self.chessPieceFlyweight = ChessPieceFlyweightFactory.getChessPieceFlyweight(color: color)
即便创建若干个棋子,真实的 ChessPieceFlyweight 只有两个,随着创建的个数越多,节省的内存也越多。
组合模式
采用树状层级结构来表示部分与整体的关系,使得无论是整体对象还是单个对象,对其访问都具有一致性。 在面向对象设计思想中,完整的文件系统至少需要两个类来描述,文件夹和文件;文件系统实际就是树状层级结构,可以使用组合模式设计。
重构后
enum NodeType
case Folder
case File
protocol FileNode
var type: NodeType get
var name: String get
func addNode(node: FileNode)
func removeNode(node: FileNode)
func getAllNode() -> [FileNode]
class file: FileNode
var type: NodeType
var name: String
var child = [FileNode]()
init(type: NodeType, name: String)
self.type = type
self.name = name
func addNode(node: FileNode)
self.child.append(node)
func removeNode(node: FileNode)
self.child = self.child.filter( n in
if node.name == n.name && node.type == n.type
return false
return true
)
func getAllNode() -> [FileNode]
return self.child
通过定义统一的 FileNode
接口,使得使用方无论关心当前操作的节点是文件夹还是文件,都有统一的访问方式,而且屏蔽了树结构中层级的概念,这是组合模式最大的优势。
以上是关于设计模式-结构型的主要内容,如果未能解决你的问题,请参考以下文章
新产品立项了,作为嵌入式软件工程师该如何来开展设计工作?(个人经验总结分享)...