[Design Pattern] Structural Pattern

[Design Pattern] Structural Pattern

A design pattern is a general solution to a certain type of problem in certain scenarios.

Using design patterns can make code reusable, make code easier for others to understand, and ensure code reliability.

Design patterns are generally divided into three categories:

  • Creational mode: the mode of object instantiation, the creational mode is used to decouple the instantiation process of objects
  • Behavioral patterns: how classes and objects interact, with division of responsibilities and algorithms
  • Structural patterns: combine classes or objects together to form a larger structure

This article will describe the summary of the use of structural patterns .

structural pattern

adapter pattern

The adapter pattern acts as a bridge between two incompatible interfaces. It combines the functionality of two separate interfaces.

It converts the interface of a class into another interface that the client wishes. The Adapter pattern enables classes that would otherwise not work together due to incompatible interfaces to work together.

For example, the card reader is used as an adapter between the memory card and the notebook, and then the memory card is inserted into the card reader, and then the card reader is inserted into the notebook, so that the memory card can be read through the notebook.

Adapters are not added at detailed design time, but to solve problems for items that are in service.

advantage:

  • Any two unrelated classes can be run together
  • Improved class reuse
  • Increased class transparency
  • good flexibility

shortcoming:

Excessive use of adapters will make the system very messy and difficult to grasp as a whole. For example, it is obvious that the A interface is called, but the internal implementation is actually adapted to the implementation of the B interface . If this happens too much in a system, it is tantamount to a disaster. Therefore, if it is not necessary, you can directly refactor the system without using the adapter.

Code demo:

Let's demonstrate the use of the adapter pattern. First, the audio player device can only play mp3 files, now by using a more advanced audio player to play vlc and mp4 files.

Step 1: Create Interfaces for Media Player and More Advanced Media Players

interface MediaPlayer {
    
    
    fun play(audioType: String, fileName: String)
}

interface AdvancedMediaPlayer {
    
    
    fun playVlc(fileName: String)
    fun playMp4(fileName: String)
}

Step 2: Create an entity class that implements the AdvancedMediaPlayer interface

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")
    }
}

Step 3: Create an adapter class that implements the MediaPlayer interface

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()
        }
    }
}

Step 4: Create an entity class that implements the MediaPlayer interface

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")
        }
    }
}

Step 5: Test the code, use AudioPlayer to play different types of audio formats

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

Flyweight mode

Flyweight mode is mainly used to reduce the number of created objects to reduce memory usage and improve performance.

The Flyweight pattern tries to reuse existing objects of the same kind, and if no matching object is found, a new object is created.

When there are a large number of objects, it may cause memory overflow. We abstract the common parts. If there are the same business requests, we will directly return the existing objects in memory to avoid recreation.

For example, if there is a String in java, it will be returned, if not, a string will be created and stored in the string buffer pool.

In addition, using the Flyweight mode will increase the complexity of the system, and the external state and internal state need to be separated, and the external state has an inherent nature and should not change with the internal state, otherwise it will cause system chaos.

Code demo:

We will demonstrate this pattern by creating 5 objects to draw 20 circles at different positions. Since there are only 5 colors available, the color property is used to check for an existing Circle object. Key code: use HashMap to store these objects.

Step 1: Create a Shape interface

interface Shape {
    
    
    fun draw()
}

Step 2: Create an implementation class that implements the Shape interface

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")
    }
}

Step 3: Create a factory to reuse or generate objects of entity classes based on given information

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
    }
}

Step 4: Test the code, use the factory to obtain the object of the entity class by passing the color information

// 测试代码:使用该工厂,通过传递颜色信息来获取实体类的对象
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

decorator pattern

Generally, we often use inheritance to extend a class, because inheritance introduces static features to the class, and with the increase of extended functions, subclasses will expand. So to extend a class without adding many subclasses , we can use the decorator pattern.

The decorator pattern allows adding new functionality to an existing object without changing its structure.

We created a decoration class to wrap the original class and provide additional functionality while maintaining the integrity of the class method signature.

The decoration mode is an alternative mode of inheritance . The decoration class and the decorated class can develop independently without coupling with each other, but multi-layer decoration will make the code more complicated .

Code demo:

We will decorate a shape with a different color without changing the shape class.

Step 1: Create a Shape interface

interface Shape {
    
    
    fun draw()
}

Step 2: Create an entity class that implements the interface

class Rectangle : Shape {
    
    
    override fun draw() {
    
    
        println("Shape: Rectangle")
    }
}

class Circle : Shape {
    
    
    override fun draw() {
    
    
        println("Shape: Circle")
    }
}

Step 3: Create an abstract decoration class that implements the Shape interface

abstract class ShapeDecorator(val decoratedShape: Shape) : Shape {
    
    
    override fun draw() {
    
    
        decoratedShape.draw()
    }
}

Step 4: Create an entity decoration class that extends the ShapeDecorator class

class RedShapeDecorator(decoratedShape: Shape) : ShapeDecorator(decoratedShape) {
    
    
    override fun draw() {
    
    
        super.draw()
        setRedBorder()
    }

    private fun setRedBorder() {
    
    
        println("Border Color: Red")
    }
}

Step Five: Test the Code

@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

appearance mode

The facade mode hides the complexity of the system and provides the client with an interface through which the client can access the system.

To provide a consistent interface for a set of interfaces in a subsystem, the facade pattern defines a high-level interface that makes the subsystem easier to use .

advantage:

  • Reduce system interdependence
  • increase flexibility
  • Improved security

**Disadvantages:** Does not conform to the principle of opening and closing. If you want to change something, it is very troublesome, and inheritance and rewriting are not suitable.

Code demo:

Step 1: Create a Shape interface

interface Shape {
    
    
    fun draw()
}

Step 2: Create an entity class that implements the interface

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()")
    }
}

Step 3: Create a Appearance Class

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()
    }
}

Step 4: Test the code

@JvmStatic
fun main(args: Array<String>) {
    
    
    val shapeMaker = ShapeMaker()
    shapeMaker.drawCircle()
    shapeMaker.drawRectangle()
    shapeMaker.drawSquare()
}

// 输出结果:
Circle::draw()
Rectangle::draw()
Square::draw()

combination mode

Composite pattern, also known as partial whole pattern, treats a group of similar objects as a single object . The Composite pattern composes objects according to a tree structure, used to represent part and whole hierarchies.

For example, an arithmetic expression includes an operand, an operator, and another operand, where the other operand may also be the operand, the operator, and the other operand.

advantage:

  • Simple calling of high-level modules
  • Nodes can be added freely

Disadvantages: When using the composite mode, the declarations of its leaves and branches are implementation classes , not interfaces , which violates the principle of dependency inversion.

Code demo:

Demonstrates the hierarchy of employees in an organization.

Step 1: Create an Employee class with a list of Employee objects

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()
    }
}

Step 2: Test the code, use the Employee class to create and print employee hierarchies

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 ]

bridge mode

The bridge mode is used to decouple abstraction from implementation so that the two can change independently. It decouples abstraction and implementation by providing a bridging structure between them.

This pattern involves an interface acting as a bridge, making the functionality of the entity class independent of the interface implementing class. Both types of classes can be structurally changed independently of each other.

scenes to be used:

  • Do not want to use inheritance or the number of system classes will increase dramatically due to multi-level inheritance
  • Two independently varying dimensions

advantage:

  • Separation of abstraction and implementation
  • Excellent scalability
  • Realize details to customers

**Disadvantages:** The introduction of the bridge mode will increase the difficulty of system understanding and design. Since the aggregation relationship is established on the abstract layer, developers are required to design and program for abstraction.

Code demo:

Use the same abstract class method but different bridge implementation classes to draw circles of different colors.

Step 1: Create a bridge to implement the interface

interface DrawAPI {
    
    
    fun drawCircle(radius: Int, x: Int, y: Int)
}

Step 2: Create two bridge implementation classes

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]")
    }
}

Step 3: Create an abstract class Shape using the DrawAPI interface

abstract class Shape protected constructor(protected var drawAPI: DrawAPI) {
    
    
    abstract fun draw()
}

Step 4: Create an entity class that implements the Shape abstract class

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)
    }
}

Step Five: Test the Code

@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]

Proxy mode

The proxy mode is divided into static proxy and dynamic proxy. It has been written in the previous article, so I won’t go into details here.

Portal: [Design Pattern] Static Proxy and Dynamic Proxy

Guess you like

Origin blog.csdn.net/yang553566463/article/details/124592766