【设计模式】之 结构型模式
设计模式就是在某些场景下,针对某类问题的某种通用的解决方案。
使用设计模式可以使得代码可复用、让代码更容易被他人理解、保证代码可靠性。
设计模式总体被分为三类:
- 创建型模式:对象实例化的模式,创建型模式用于解耦对象的实例化过程
- 行为型模式:类和对象如何交互,及划分责任和算法
- 结构型模式:把类或对象结合在一起形成一个更大的结构
本文将讲述结构型模式的使用总结。
结构型模式
适配器模式
适配器模式是作为两个不兼容的接口之间的桥梁。它结合了两个独立接口的功能。
它将一个类的接口转换成客户希望的另外一个接口。适配器模式使得原本由于接口不兼容而不能一起工作的那些类可以一起工作。
例如,读卡器是作为内存卡和笔记本之间的适配器,然后将内存卡插入读卡器,再将读卡器插入笔记本,这样就可以通过笔记本来读取内存卡。
适配器不是在详细设计时添加的,而是解决正在服役的项目的问题。
优点:
- 可以让任何两个没有关联的类一起运行
- 提高了类的复用
- 增加了类的透明度
- 灵活性好
缺点:
过多地使用适配器,会让系统非常零乱,不易整体进行把握。比如,明明看到调用的是 A 接口,其实内部被适配成了 B 接口的实现,一个系统如果太多出现这种情况,无异于一场灾难。因此如果不是很有必要,可以不使用适配器,而是直接对系统进行重构。
代码演示:
下面我们来演示适配器模式的使用。首先,音频播放器设备只能播放 mp3 文件,现在通过使用一个更高级的音频播放器来播放 vlc 和 mp4 文件。
步骤1:为媒体播放器和更高级的媒体播放器创建接口
interface MediaPlayer {
fun play(audioType: String, fileName: String)
}
interface AdvancedMediaPlayer {
fun playVlc(fileName: String)
fun playMp4(fileName: String)
}
步骤2:创建实现了 AdvancedMediaPlayer 接口的实体类
class VlcPlayer : AdvancedMediaPlayer {
override fun playVlc(fileName: String) {
println("Playing vlc file. Name: $fileName")
}
override fun playMp4(fileName: String) {
//什么也不做
}
}
class Mp4Player : AdvancedMediaPlayer {
override fun playVlc(fileName: String) {
//什么也不做
}
override fun playMp4(fileName: String) {
println("Playing mp4 file. Name: $fileName")
}
}
步骤3:创建实现了 MediaPlayer 接口的适配器类
class MediaAdapter(audioType: String) : MediaPlayer {
var advancedMusicPlayer: AdvancedMediaPlayer? = null
override fun play(audioType: String, fileName: String) {
if (audioType == "vlc") {
advancedMusicPlayer?.playVlc(fileName)
} else if (audioType == "mp4") {
advancedMusicPlayer?.playMp4(fileName)
}
}
init {
if (audioType == "vlc") {
advancedMusicPlayer = VlcPlayer()
} else if (audioType == "mp4") {
advancedMusicPlayer = Mp4Player()
}
}
}
步骤4:创建实现了 MediaPlayer 接口的实体类
class AudioPlayer() : MediaPlayer {
var mediaAdapter: MediaAdapter? = null
override fun play(audioType: String, fileName: String) {
//播放 mp3 音乐文件的内置支持
if (audioType == "mp3") {
println("Playing mp3 file. Name: $fileName")
} else if (audioType == "vlc" || audioType == "mp4") {
mediaAdapter = MediaAdapter(audioType)
mediaAdapter?.play(audioType, fileName)
} else {
println("Invalid media. $audioType format not supported")
}
}
}
步骤5:测试代码,使用 AudioPlayer 来播放不同类型的音频格式
fun main(args: Array<String>) {
val audioPlayer = AudioPlayer()
audioPlayer.play("mp3", "beyond the horizon.mp3")
audioPlayer.play("mp4", "alone.mp4")
audioPlayer.play("vlc", "far far away.vlc")
audioPlayer.play("avi", "mind me.avi")
}
// 运行结果:
Playing mp3 file. Name: beyond the horizon.mp3
Playing mp4 file. Name: alone.mp4
Playing vlc file. Name: far far away.vlc
Invalid media. avi format not supported
享元模式
享元模式主要用于减少创建对象的数量,以减少内存占用和提高性能。
享元模式尝试重用现有的同类对象,如果未找到匹配的对象,则创建新对象。
在有大量对象时,有可能会造成内存溢出,我们把其中共同的部分抽象出来,如果有相同的业务请求,直接返回在内存中已有的对象,避免重新创建。
例如,java 中的 String,如果有则返回,如果没有则创建一个字符串保存在字符串缓存池里面。
另外,使用享元模式,会提高系统的复杂度,需要分离出外部状态和内部状态,而且外部状态具有固有化的性质,不应该随着内部状态的变化而变化,否则会造成系统的混乱。
代码演示:
我们将通过创建 5 个对象来画出 20 个分布于不同位置的圆来演示这种模式。由于只有 5 种可用的颜色,所以 color 属性被用来检查现有的 Circle 对象。关键代码:用 HashMap 存储这些对象。
步骤1:创建一个 Shape 接口
interface Shape {
fun draw()
}
步骤2:创建一个实现 Shape 接口的实现类
class Circle(private val color: String) : Shape {
private var x = 0
private var y = 0
private var radius = 0
fun setX(x: Int) {
this.x = x
}
fun setY(y: Int) {
this.y = y
}
fun setRadius(radius: Int) {
this.radius = radius
}
override fun draw() {
println("Circle: Draw() [Color : $color, x : $x, y :$y, radius :$radius")
}
}
步骤3:创建一个工厂,复用 或 生成基于给定信息的实体类的对象
object ShapeFactory {
private val circleMap: HashMap<String, Shape> = HashMap()
fun getCircle(color: String): Shape {
var circle = circleMap[color] as Circle?
if (circle == null) {
circle = Circle(color)
circleMap[color] = circle
println("Creating circle of color : $color")
}
return circle
}
}
步骤四:测试代码,使用该工厂,通过传递颜色信息来获取实体类的对象
// 测试代码:使用该工厂,通过传递颜色信息来获取实体类的对象
object FlyweightPatternDemo {
private val colors = arrayOf("Red", "Green", "Blue", "White", "Black")
private val randomColor: String
get() = colors[(Math.random() * colors.size).toInt()]
private val randomX: Int
get() = (Math.random() * 100).toInt()
private val randomY: Int
get() = (Math.random() * 100).toInt()
@JvmStatic
fun main(args: Array<String>) {
for (i in 0..19) {
val circle = ShapeFactory.getCircle(randomColor) as Circle
circle.setX(randomX)
circle.setY(randomY)
circle.setRadius(100)
circle.draw()
}
}
}
// 输出结果:
Creating circle of color : Black
Circle: Draw() [Color : Black, x : 36, y :71, radius :100
Creating circle of color : Green
Circle: Draw() [Color : Green, x : 27, y :27, radius :100
Creating circle of color : White
Circle: Draw() [Color : White, x : 64, y :10, radius :100
Creating circle of color : Red
Circle: Draw() [Color : Red, x : 15, y :44, radius :100
Circle: Draw() [Color : Green, x : 19, y :10, radius :100
Circle: Draw() [Color : Green, x : 94, y :32, radius :100
Circle: Draw() [Color : White, x : 69, y :98, radius :100
Creating circle of color : Blue
Circle: Draw() [Color : Blue, x : 13, y :4, radius :100
Circle: Draw() [Color : Green, x : 21, y :21, radius :100
Circle: Draw() [Color : Blue, x : 55, y :86, radius :100
Circle: Draw() [Color : White, x : 90, y :70, radius :100
Circle: Draw() [Color : Green, x : 78, y :3, radius :100
Circle: Draw() [Color : Green, x : 64, y :89, radius :100
Circle: Draw() [Color : Blue, x : 3, y :91, radius :100
Circle: Draw() [Color : Blue, x : 62, y :82, radius :100
Circle: Draw() [Color : Green, x : 97, y :61, radius :100
Circle: Draw() [Color : Green, x : 86, y :12, radius :100
Circle: Draw() [Color : Green, x : 38, y :93, radius :100
Circle: Draw() [Color : Red, x : 76, y :82, radius :100
Circle: Draw() [Color : Blue, x : 95, y :82, radius :100
装饰模式
一般的,我们为了扩展一个类经常使用继承方式实现,由于继承为类引入静态特征,并且随着扩展功能的增多,子类会很膨胀。所以在不想增加很多子类的情况下扩展类,我们就可以使用装饰器模式。
装饰器模式允许向一个现有的对象添加新的功能,同时又不改变其结构。
我们创建了一个装饰类,用来包装原有的类,并在保持类方法签名完整性的前提下,提供了额外的功能。
装饰模式是继承的一个替代模式,装饰类和被装饰类可以独立发展,不会相互耦合,但是多层装饰会使得代码更加复杂。
代码演示:
我们将把一个形状装饰上不同的颜色,同时又不改变形状类。
步骤1:创建一个 Shape 接口
interface Shape {
fun draw()
}
步骤二:创建实现接口的实体类
class Rectangle : Shape {
override fun draw() {
println("Shape: Rectangle")
}
}
class Circle : Shape {
override fun draw() {
println("Shape: Circle")
}
}
步骤三:创建实现了 Shape 接口的抽象装饰类
abstract class ShapeDecorator(val decoratedShape: Shape) : Shape {
override fun draw() {
decoratedShape.draw()
}
}
步骤四:创建扩展了 ShapeDecorator 类的实体装饰类
class RedShapeDecorator(decoratedShape: Shape) : ShapeDecorator(decoratedShape) {
override fun draw() {
super.draw()
setRedBorder()
}
private fun setRedBorder() {
println("Border Color: Red")
}
}
步骤五:测试代码
@JvmStatic
fun main(args: Array<String>) {
val circle: Shape = Circle()
val redCircle: ShapeDecorator = RedShapeDecorator(Circle())
val redRectangle: ShapeDecorator = RedShapeDecorator(Rectangle())
println("Circle with normal border")
circle.draw()
println("\nCircle of red border")
redCircle.draw()
println("\nRectangle of red border")
redRectangle.draw()
}
// 输出结果
Circle with normal border
Shape: Circle
Circle of red border
Shape: Circle
Border Color: Red
Rectangle of red border
Shape: Rectangle
Border Color: Red
外观模式
外观模式,隐藏系统的复杂性,并向客户端提供了一个客户端可以访问系统的接口。
为子系统中的一组接口提供一个一致的界面,外观模式定义了一个高层接口,这个接口使得这一子系统更加容易使用。
优点:
- 减少系统相互依赖
- 提高灵活性
- 提高了安全性
**缺点:**不符合开闭原则,如果要改东西很麻烦,继承重写都不合适。
代码演示:
步骤1:创建一个 Shape 接口
interface Shape {
fun draw()
}
步骤二:创建实现接口的实体类
class Rectangle : Shape {
override fun draw() {
println("Rectangle::draw()")
}
}
class Square : Shape {
override fun draw() {
println("Square::draw()")
}
}
class Circle : Shape {
override fun draw() {
println("Circle::draw()")
}
}
步骤三:创建一个外观类
class ShapeMaker {
private val circle: Shape
private val rectangle: Shape
private val square: Shape
fun drawCircle() {
circle.draw()
}
fun drawRectangle() {
rectangle.draw()
}
fun drawSquare() {
square.draw()
}
init {
circle = Circle()
rectangle = Rectangle()
square = Square()
}
}
步骤四:测试代码
@JvmStatic
fun main(args: Array<String>) {
val shapeMaker = ShapeMaker()
shapeMaker.drawCircle()
shapeMaker.drawRectangle()
shapeMaker.drawSquare()
}
// 输出结果:
Circle::draw()
Rectangle::draw()
Square::draw()
组合模式
组合模式,又叫部分整体模式,把一组相似的对象当作一个单一的对象。组合模式依据树形结构来组合对象,用来表示部分以及整体层次。
例如,算术表达式包括操作数、操作符和另一个操作数,其中,另一个操作数也可以是操作数、操作符和另一个操作数。
优点:
- 高层模块调用简单
- 节点自由增加
缺点:在使用组合模式时,其叶子和树枝的声明都是实现类,而不是接口,违反了依赖倒置原则。
代码演示:
演示一个组织中员工的层次结构。
步骤一:创建 Employee 类,该类带有 Employee 对象的列表
class Employee(private val name: String, private val dept: String, private val salary: Int) {
// 下属列表
private val subordinates: MutableList<Employee>
fun add(e: Employee) {
subordinates.add(e)
}
fun remove(e: Employee) {
subordinates.remove(e)
}
fun getSubordinates(): List<Employee> {
return subordinates
}
override fun toString(): String {
return ("Employee :[ Name : " + name
+ ", dept : " + dept + ", salary :"
+ salary + " ]")
}
init {
subordinates = ArrayList()
}
}
步骤二:测试代码,使用 Employee 类来创建和打印员工的层次结构
object CompositePatternDemo {
@JvmStatic
fun main(args: Array<String>) {
val ceo = Employee("John", "CEO", 30000)
val headSales = Employee("Robert", "Head Sales", 20000)
val headMarketing = Employee("Michel", "Head Marketing", 20000)
val clerk1 = Employee("Laura", "Marketing", 10000)
val clerk2 = Employee("Bob", "Marketing", 10000)
val salesExecutive1 = Employee("Richard", "Sales", 10000)
val salesExecutive2 = Employee("Rob", "Sales", 10000)
ceo.add(headSales)
ceo.add(headMarketing)
headSales.add(salesExecutive1)
headSales.add(salesExecutive2)
headMarketing.add(clerk1)
headMarketing.add(clerk2)
//打印该组织的所有员工
println(ceo)
for (headEmployee in ceo.getSubordinates()) {
println(headEmployee)
for (employee in headEmployee.getSubordinates()) {
println(employee)
}
}
}
}
// 结果输出:
Employee :[ Name : John, dept : CEO, salary :30000 ]
Employee :[ Name : Robert, dept : Head Sales, salary :20000 ]
Employee :[ Name : Richard, dept : Sales, salary :10000 ]
Employee :[ Name : Rob, dept : Sales, salary :10000 ]
Employee :[ Name : Michel, dept : Head Marketing, salary :20000 ]
Employee :[ Name : Laura, dept : Marketing, salary :10000 ]
Employee :[ Name : Bob, dept : Marketing, salary :10000 ]
桥接模式
桥接模式,是用于把抽象化与实现化解耦,使得二者可以独立变化。它通过提供抽象化和实现化之间的桥接结构,来实现二者的解耦。
这种模式涉及到一个作为桥接的接口,使得实体类的功能独立于接口实现类。这两种类型的类可被结构化改变而互不影响。
使用场景:
- 不希望使用继承或因为多层次继承导致系统类的个数急剧增加
- 两个独立变化的维度
优点:
- 抽象和实现的分离
- 优秀的扩展能力
- 实现细节对客户透
**缺点:**桥接模式的引入会增加系统的理解与设计难度,由于聚合关联关系建立在抽象层,要求开发者针对抽象进行设计与编程。
代码演示:
使用相同的抽象类方法但是不同的桥接实现类,来画出不同颜色的圆。
步骤一:创建桥接实现接口
interface DrawAPI {
fun drawCircle(radius: Int, x: Int, y: Int)
}
步骤二:创建两个桥接实现类
class RedCircle : DrawAPI {
override fun drawCircle(radius: Int, x: Int, y: Int) {
println("Drawing Circle[ color: red, radius: $radius, x: $x, $y]")
}
}
class GreenCircle : DrawAPI {
override fun drawCircle(radius: Int, x: Int, y: Int) {
println("Drawing Circle[ color: green, radius: $radius, x: $x, $y]")
}
}
步骤三:使用 DrawAPI 接口创建抽象类 Shape
abstract class Shape protected constructor(protected var drawAPI: DrawAPI) {
abstract fun draw()
}
步骤四:创建实现了 Shape 抽象类的实体类
class Circle(
private val x: Int,
private val y: Int,
private val radius:
Int, drawAPI: DrawAPI
) : Shape(drawAPI) {
override fun draw() {
drawAPI.drawCircle(radius, x, y)
}
}
步骤五:测试代码
@JvmStatic
fun main(args: Array<String>) {
val redCircle: Shape = Circle(100, 100, 10, RedCircle())
val greenCircle: Shape = Circle(100, 100, 10, GreenCircle())
redCircle.draw()
greenCircle.draw()
}
// 输出结果:
Drawing Circle[ color: red, radius: 10, x: 100, 100]
Drawing Circle[ color: green, radius: 10, x: 100, 100]
代理模式
代理模式又分为静态代理和动态代理,之前的文章中写过,这里就不赘述了。
传送门:【设计模式】之静态代理和动态代理