An article to get "Design Patterns in Practical Combat: Android Version"

foreword

In fact, most of them may be the same as me, under the accumulated experience of developing projects and the vague memory of design patterns.
In the process of developing the project, the design pattern has actually been used, but I can't know it.

For example: A dialogue IM based on AI developed before, which involves many design patterns. But they are all used subconsciously, and even the design pattern he is a bit vague.
(ps: I remember the singleton mode the most familiar)

This article is also a serious summary of some design patterns encountered in the previous IM development. I also hope that readers can suddenly realize it! ! !
Why! ! ! ! It turns out that my code uses the XXX design pattern.
The following is an analysis of the design patterns I use in turn (the code is simplified by me).

singleton design pattern

Mode Selection Instructions

Explanation: This is a very common design pattern, I believe everyone has used it. Almost all the Manager classes in the system are implemented through singletons
. Usage scenario: For objects that are frequently created and destroyed, and you want to preserve the state of this class globally, you can use it for me! ! ! ! !
Purpose: To ensure that a class has only one instance and provide a global access point to the instance
Design pattern category: Creational pattern

Scene reproduction:

That must be a network request management class

  • Objects need to be created and destroyed frequently
  • requires global access
  • Save some shared data such as login credentials and user information to facilitate the use of other modules

Let’s just say, isn’t this a proper scenario that needs to use the singleton mode?
First, let’s talk about some of the editor’s favorite implementations (JAVA and Kotlin ps: it’s all because it’s easy to write and applies to all scenarios)
Other singletons For details, please refer to the "Singleton Design Pattern" of Design Patterns
Java (the way of static inner classes)

public class OkHttpManager {
    
    
    OkHttpManager(){
    
    }

    private static class OkHttpManagerHandle{
    
    
        static OkHttpManager INSTANCE = new OkHttpManager();
    }

    public static OkHttpManager getInstance(){
    
    
        return OkHttpManagerHandle.INSTANCE;
    }
}

Java (double check lock)

private volatile static OkHttpManager INSTANCE;

OkHttpManager(){
    
    }

public static OkHttpManager getInstance(){
    
    
    if (INSTANCE == null){
    
    
        synchronized (OkHttpManager.class){
    
    
            if (INSTANCE == null){
    
    
                INSTANCE = new OkHttpManager();
            }
        }
    }
    return INSTANCE;
}

Kotlin (hungry Chinese style)

object OkHttpManager {
    
     }

Kotlin (double check lock)

class OkHttpManager {
    
    
    companion object {
    
    
        @Volatile
        private var INSTANCE: OkHttpManager? = null

        fun getInstance(): OkHttpManager {
    
    
            return INSTANCE ?: synchronized(this) {
    
    
                return INSTANCE ?: OkHttpManager().also {
    
     INSTANCE = it }
            }
        }
    }
}

builder design pattern

The builder mode is a bit too common. Larger frameworks such as OkHttp and Glide use the builder mode to create objects, which is convenient for users to customize to a certain extent.

Mode Selection Instructions

Instructions: Use multiple simple objects to build a complex object step by step.
Usage scenario: When the number of constructor parameters of a class exceeds 4, and some of these parameters are optional parameters, and some basic components will not change, consider using the constructor mode.
Purpose: Solve the construction of complex objects
Design pattern category: Creational pattern

Scene recurrence

That must be a custom Dialog, brothers, a lot of Dialog needs to be used in the project, and the IM module is also essential. The original sound does not meet the requirements, but you can't just customize a Dialog when you encounter a scene.
We directly customize a global use, and then use the builder mode to control its function, style, and event processing. Let’s do it
! ! !

  • Step 1: Inherit Dialog and initialize it
class CustomDialog private constructor(
    context: Context,
    private val title: String?,
    private val content: String?,
    private val positiveButton: String?,
    private val negativeButton: String?,
    private val cancelable: Boolean,
    private val backgroundColor: Int
) : Dialog(context) {
    
    

    init {
    
    
        setContentView(createView())
        setCancelable(cancelable)
    }
    
    private fun createView(): View {
    
    
        //这部分省略了就是设置一些布局相关的,当然你可以自定义布局
    }
  • Step 2: Use the static inner class to build a chain call to set the property
class Builder(private val context: Context) {
    
    
    private var title: String? = null
    private var content: String? = null
    private var positiveButton: String? = null
    private var negativeButton: String? = null
    private var cancelable: Boolean = true
    private var backgroundColor: Int = Color.WHITE

    fun setTitle(title: String): Builder {
    
    
        this.title = title
        return this
    }

    //一些set方法就省略了

    fun setBackgroundColor(backgroundColor: Int): Builder {
    
    
        this.backgroundColor = backgroundColor
        return this
    }

    fun build(): CustomDialog {
    
    
        return CustomDialog(
            context,
            title,
            content,
            positiveButton,
            negativeButton,
            cancelable,
            backgroundColor
        )
    }
}
  • Step 3: Chain calls to build objects
val dialog = CustomDialog.Builder(context)
    .setTitle("Dialog Title")
    .setContent("Dialog Content")
    .setPositiveButton("OK")
    .setNegativeButton("Cancel")
    .setCancelable(true)
    .setBackgroundColor(Color.YELLOW)
    .build()

dialog.show()

Of course, this is just an example. In a real project, the Dialog customized by the builder is used. Every content supports customization, including the layout style of the OK and Cancel buttons.

factory design pattern

Let me first talk about the difference between the factory mode and the above strategy mode, so that everyone will not be confused, because the two modes are very similar.

  • Difference 1: different uses
    • The factory pattern is a creational pattern, which creates objects.
    • A strategy is a behavioral pattern, which allows the created object to choose a method behavior of its own.
  • Difference 2: different use time
    • The caller of the factory method pattern can directly call the method attributes of the factory method instance, etc.
    • The strategy mode cannot directly call the method attribute of the instance, it needs to be called after encapsulating the strategy in the strategy class.
  • Summary of the difference: That is to say, the factory is diversified by creating objects, and the strategy is diversified by selecting behaviors.
  • Combined use: the factory pattern can be used to create a variety of strategy objects.

Mode Selection Instructions

Explanation: This is a very common creational design pattern. This method separates the creation of our objects from the use code, and it does not expose the creation logic of internal objects to the outer layer. Use scenarios: design to multiple
types Object creation, and these various types are for a business scenario. Just change it to factory mode for me.
Purpose: Create a factory interface for product objects, let its subclasses decide which class to instantiate, and defer the actual creation work to subclasses.
Design pattern category: Creational mode

Scene recurrence

For various types of Messages, I want to use the HandleMessage method uniformly.
Because for IM messages, I will definitely involve getting internal method attributes and so on. So I choose the factory design pattern here. The strategy mode will only handle my message.
Original code scene:

  • According to different types of IM messages, instantiate different objects and call HandleMessage
  • Such as picture, video, text, voice
if (msgType = "文本") {
    
    
    TextMessageStrategy().handleMessage(messageData)
} else if(msgType = "图片") {
    
    
    ImageMessageStrategy().handleMessage(messageData)
} else if(msgType = "视频") {
    
    
    VideoMessageStrategy().handleMessage(messageData)
} else {
    
    
    DefaultMessageStrategy().handleMessage(messageData)
}

At this time, think about how uncomfortable this code is when you have more IM types, and if multiple people develop different IM message types, everyone has to operate this code. There is no decoupling at all.
If you want to manipulate this code in the outer layer, you have to throw the object you created.
Use the factory pattern to improve:

  • Step 1: Create a unified handleMessage interface
interface IMessageHandle {
    
    
    //属于哪种文件解析类型 MessageTypeResolveEnum 用枚举类定义的IM类型
    fun getMessageType(): MessageTypeResolveEnum?

    //具体的处理消息方法
    fun handleMessage(messageData: BaseMessageData?)
}
  • Step 2: Unify the implementation of IMessageHandle
// 文本消息
class TextMessage : IMessageHandle {
    
    
    //文本类型
    override fun getMessageType(): MessageTypeResolveEnum {
    
    
        return MessageTypeResolveEnum.TEXT
    }
    //处理文本
    override fun handleMessage(messageData: BaseMessageData?) {
    
    
        println("处理文本消息 " + messageData.getContent())
    }
}


//图片的消息
class ImageMessage: IMessageHandle {
    
    
    override fun getMessageType(): MessageTypeResolveEnum? {
    
    
        MessageTypeResolveEnum.IMAGE
    }

    override fun handleMessage(messageData: BaseMessageData?) {
    
    
        println("处理图片消息 " + messageData.getContent())
    }
}

//其他默认消息
class DefaultMessage: IMessageHandle {
    
    
    override fun getMessageType(): MessageTypeResolveEnum? {
    
    
        MessageTypeResolveEnum.DEFAULT
    }

    override fun handleMessage(messageData: BaseMessageData?) {
    
    
        println("处理其他默认消息 " + messageData.getContent());
    }
}
  • Step 3: Create the factory
class IMessageFactory {
    
    
    companion object {
    
    
        fun parse(type: Int): IMessageStrategy? {
    
    
            //其他统一处理
            return parseIMessage(type)
        }

        private fun parseIMessage(type: Int): IMessageStrategy? {
    
    
            var message: IMessageStrategy? = null
            when (type) {
    
    
                MessageTypeResolveEnum.TEXT.type -> {
    
    
                    message = TextMessageStrategy()
                }

                MessageTypeResolveEnum.IMAGE.type -> {
    
    
                    message = TextMessageStrategy()
                }

                MessageTypeResolveEnum.DEFAULT.type -> {
    
    
                    message = TextMessageStrategy()
                }
            }
            return message;
        }
    }
}
  • Step Four: Use
val messageData: BaseMessageData = BaseMessageData("hahaha", MessageTypeResolveEnum.TEXT)
val message = IMessageFactory.parse(MessageTypeResolveEnum.TEXT.type)
IMAdapter.addItem(message)
message?.getMessageType()
message?.handleMessage(messageData)

Strategy Design Pattern

The strategy mode must be used less than the factory mode, because there are not so many multi-strategy scenarios.
The most common types: discounts for products at different membership levels, processing of multiple file types, multiple types of log console output logs, file record logs, database record logs, etc.
In fact, the explanation of member strategies is relatively clear here, but this article is a summary of the IM I developed, so let’s talk about different strategies for file types.

Mode Selection Instructions

Explanation: I want to make the behavior of a class or its algorithm changeable at runtime.
Usage scenario: A system has many classes, and what distinguishes them is their direct behavior (if a system has more than four strategies, it is necessary to consider using a mixed mode to solve the problem of strategy class expansion) Purpose: There are
many In the case of similar algorithms, solve a large number of if else judgments
Design pattern category: behavioral pattern

Here again, the difference from the factory design pattern is emphasized, because the above IM message will involve internal message attributes, so I use the factory design pattern.
So in what scenario do you use the strategy design pattern?
Answer: It only involves, you want to distinguish this class, only his behavior is different.

Scene recurrence

For example, I now have several types of file information: forms, PDF, WORD, flowcharts, and many other types of files. Then if they are all the same during the display, they are all displayed as files in the message. But it opens differently after I click. So we have to create a variety of file objects? Only the opening method is different, everything else is the same.
So at this time, is it in line with the problem solved by our strategy model, he just opens the behavior differently.
Original code :

if (fileType = "表格文件") {
    
    
    // dosomething
} else if(fileType = "PDF文件") {
    
    
    // doshomething
} else if(fileType = "WORD文件") {
    
    
    // doshomething
} else {
    
    
    // doshomething
}

If there is a distinction between members and non-members, there are membership levels and so on. So, isn't this increasing, so what should we do?
Take advantage of the strategy design pattern to improve:

  • Step 1: Define the Unified Policy Interface
interface FileMessageStrategy {
    
    
    //属于哪种文件解析类型 FileMessageTypeEnum 用枚举类定义的IM类型
    fun getFileType(): FileMessageTypeEnum?

    //封装的公用算法(具体的打开方法)
    fun openFile(messageData: BaseMessageData?)
}
  • Step 2: Implementation of processing strategies for different messages
// 表格文件消息
class TableMessageStrategy : FileMessageStrategy {
    
    
    //表格类型
    override fun getFileType(): FileMessageTypeEnum {
    
    
        return FileMessageTypeEnum.TABLE
    }
    //打开表格文件
    override fun openFile(messageData: BaseMessageData?) {
    
    
        println("打开表格文件 " + messageData.getContent())
    }
}

// PDF文件消息
class PDFMessageStrategy : FileMessageStrategy {
    
    
    //表格类型
    override fun getFileType(): FileMessageTypeEnum {
    
    
        return FileMessageTypeEnum.PDF
    }
    //打开PDF文件
    override fun openFile(messageData: BaseMessageData?) {
    
    
        println("打开PDF文件 " + messageData.getContent())
    }
}


// word文件消息
class WordMessageStrategy : FileMessageStrategy {
    
    
    //word类型
    override fun getFileType(): FileMessageTypeEnum {
    
    
        return FileMessageTypeEnum.WORD
    }
    //打开word文件
    override fun openFile(messageData: BaseMessageData?) {
    
    
        println("打开word文件 " + messageData.getContent())
    }
}
  • Step 3: Bind file types and related processing
class BaseFileMessageStrategy {
    
    
    private var messageMap: MutableMap<FileMessageTypeEnum, FileMessageStrategy> = ConcurrentHashMap()

    // 处理消息
    fun openFile(messageData: BaseMessageData) {
    
    
        val fileMessageStrategy: FileMessageStrategy? = messageMap[messageData.type]
        fileMessageStrategy?.let {
    
    
            it.openFile(messageData)
        }
    }

    // 添加消息处理策略
    fun putFileMessageStrategy(messageType: FileMessageTypeEnum, fileMessageStrategy: FileMessageStrategy) {
    
    
        messageMap[messageType] = fileMessageStrategy
        //可以通过工厂模式创建具体的策略对象,工厂模式负责根据需求创建不同的策略实例。
        //这样,在策略模式中可以通过工厂模式动态切换具体的策略,并且可以随时添加新的策略。
        //具体在下面通过工厂模式去讲,就是通过工厂去按照messageType去创建messageStrategy
    }
}

//使用
BaseFileMessageStrategy().openFile(messageData)

decorator design pattern

Simply think, he is to extend the function of a class. Then someone asked, if you extend the function, can't you just use inheritance?
Methods can be added and methods rewritten. That is indeed the case, and inheritance can indeed fulfill your requirements for extending functions.
But you will encounter the following problems:

  • Strong dependencies, strong fixed issues
    • When you continue to expand with new functions, do you have to continue to inherit subclasses? When you think about the series of subcategories at that time, the difficulty of maintaining and checking at that time, you want to die.
    • When you think about removing a certain intermediate iteration function in the later maintenance, at this time, because you have passed it down continuously through inheritance, it will be uncomfortable to die if you change it suddenly, and it will even affect other functions.
  • single inheritance problem
    • In case you want to extend this class, you need to inherit other classes, such as an abstract functional class. What to do?
    • Decorators can circumvent this problem
  • Dynamic addition at runtime
    • When you choose the inheritance relationship, your current function is fixed at compile time and cannot be restricted at runtime.
    • When you choose the decorator design pattern, you can dynamically add the functions to be extended while running. The
      above is why you choose the decorator pattern instead of inheritance.

Mode Selection Instructions

Description: An enhanced version of the method of inheriting and extending class functions, such as RecyclerView.Adapter in Android.
Usage scenario: Extend the function of a class, and there is a need to extend the function later. Or when inheritance cannot be used to extend the functionality of this class.
Purpose: To dynamically extend the functionality of a class without adding a large number of subclasses.
Design Pattern Category: Structural Patterns

Scene recurrence

That is to say, I now have an order function, which has a basic order class Order. There are several commonly used methods

  • get order id
  • Get order amount
public class Order {
    
    
    private String orderId;
    private double totalAmount;

    public Order(String orderId, double totalAmount) {
    
    
        this.orderId = orderId;
        this.totalAmount = totalAmount;
    }

    public String getOrderId() {
    
    
        return orderId;
    }

    public double getTotalAmount() {
    
    
        return totalAmount;
    }

    public void process() {
    
    
        System.out.println("Processing order: " + orderId);
    }
}

I now want to expand two functions, and the requirements are divided into two. Then I don't want to extend through inheritance, but decorators.

  • Step 1: Create a basic decorator class
public abstract class OrderDecorator extends Order {
    
    
    protected Order decoratedOrder;

    public OrderDecorator(Order decoratedOrder) {
    
    
        super(decoratedOrder.getOrderId(), decoratedOrder.getTotalAmount());
        this.decoratedOrder = decoratedOrder;
    }

    public void process() {
    
    
        decoratedOrder.process();
    }
}
  • Step 2: Extend the first function, order logging
public class LoggedOrder extends OrderDecorator {
    
    
    public LoggedOrder(Order decoratedOrder) {
    
    
        super(decoratedOrder);
    }

    public void process() {
    
    
        log();
        super.process();
    }

    private void log() {
    
    
        System.out.println("Logging order: " + decoratedOrder.getOrderId());
    }
}
  • Expand the second content: send email notification
public class NotificationOrder extends OrderDecorator {
    
    
    public NotificationOrder(Order decoratedOrder) {
    
    
        super(decoratedOrder);
    }

    public void process() {
    
    
        super.process();
        sendNotification();
    }

    private void sendNotification() {
    
    
        System.out.println("Sending notification for order: " + decoratedOrder.getOrderId());
    }
}
  • Step 3: use, you can dynamically switch between the two functions to use
Order order = new Order("12345", 100.0);
Order decoratedOrder;
if (version == "4.0"){
    
    
    decoratedOrder = new LoggedOrder(order);
} else {
    
    
    decoratedOrder = new NotificationOrder(order);
}
decoratedOrder.process();

See, by using the decorator design pattern, we can dynamically add different functionality to the order object without modifying the order class or creating a lot of subclasses. Greatly increased scalability and flexibility.

Adapter design pattern

The adapter mode is actually the most common, that is our commonly used list RecyclerView, its Adapter adapter.
The purpose of the Adapter pattern is to enable classes that would otherwise not work together due to incompatible interfaces to work together.
For RecyclerView, you can think of it this way: your data is the existing system, you want to convert the data into View, View is your vendor class, then Adapter is the adapter at this time. That is, the GetView function binds different data to the current View.
This picture is too graphic.
insert image description here

Mode Selection Instructions

Description: Allows classes that would otherwise not work together due to incompatible interfaces to work together. That is what we call the middle layer, which is the case of incompatible interfaces.
Usage scenario: A unified output interface is required, but the interface at the input end is unpredictable.
Purpose: To adapt the interface so that the unpredictable input end has a unified output.
Design Pattern Category: Structural Patterns

Scene recurrence

1. We are developing a payment system, and we need to adapt to two newly added third-party payment methods.
2. But in the end, we only want to pay through the previous interface.
3. At this time, we can create an adapter class to integrate these two The third-party payment interface is adapted to the previous original payment interface.
Everyone said that I can also use if else. That's true, the code looks like shit, and as your payment methods increase, and there are payment method modifications, increases, and decreases in the middle, it will become more and more difficult to maintain. This is called coupling.
Let's see what the adapter does.

  • Previous unified payment interface
public interface Payment {
    
    
    void pay(float amount);
}
  • Two newly added three-party payment interfaces: such as WeChat and Alipay
//微信
public class WeChatPay implements WeChatPayPayment {
    
    
    @Override
    public void weChatPayPay(float amount) {
    
    
        // 具体的支付逻辑
        System.out.println("Using WeChat Pay to pay: " + amount);
    }
}

//支付宝
public class Alipay implements AlipayPayment {
    
    
    @Override
    public void alipayPay(float amount) {
    
    
        // 具体的支付逻辑
        System.out.println("Using Alipay to pay: " + amount);
    }
}
  • The first step: Create an adapter class PaymentAdapter for adapting to all payment methods: (this is the way of class adapter)
public class PaymentAdapter implements Payment {
    
    
    private Object payment;

    public PaymentAdapter(Object payment) {
    
    
        this.payment = payment;
    }

    @Override
    public void pay(float amount) {
    
    
        if (payment instanceof AlipayPayment) {
    
    
            ((AlipayPayment) payment).alipayPay(amount);
        } else if (payment instanceof WeChatPayPayment) {
    
    
            ((WeChatPayPayment) payment).weChatPayPay(amount);
        }
        // 其他支付方式的调用...
    }
}
  • Step Two: Using the Adapter
//阿里支付
Alipay alipay = new Alipay();
Payment payment = new PaymentAdapter(alipay);
payment.pay(100.0f);

//微信支付        
WeChatPay weChatPay = new WeChatPay();
payment = new PaymentAdapter(weChatPay);
payment.pay(200.0f);

Of course, the knowledge has been demonstrated, how to use this adapter in a real environment. Your payment.pay is in the main class. Definitely not going to move.
And various payment methods will become involved.
So you need a unified interface to use, you can pass in the input parameters through the adapter. This avoids modifying the payment main class.

Summarize

All design patterns are designed to solve coupling, scalability, reusability, and testability.
It's not, to fix some code issues. All needs can be achieved in any way, and it is not necessary to use design patterns.
but! ! ! !
The design pattern can reflect its advantages in larger projects. Coupling, scalability, reusability, and testability greatly reduce the time for subsequent maintenance and development.
The reduced time lies in: you don’t have to worry about mobilizing the whole body (coupling), you don’t have to care about internal implementation (extensibility), you can use it right away (reusability), and fast unit testing (testability).

Guess you like

Origin blog.csdn.net/weixin_45112340/article/details/132642962