一篇文章搞定《实战中的设计模式之Android版》

一篇文章搞定《实战中的设计模式之Android版》

  • 前言
  • 单例设计模式
    • 模式选用说明
    • 场景复现:
  • 构建者设计模式
    • 模式选用说明
    • 场景复现
  • 工厂设计模式
    • 模式选用说明
    • 场景复现
  • 策略设计模式
    • 模式选用说明
    • 场景复现
  • 装饰者设计模式
    • 模式选用说明
    • 场景复现
  • 适配器设计模式
    • 模式选用说明
    • 场景复现
  • 总结

前言

其实大多数可能和我一样,在开发项目的累积经验下,和对设计模式隐约的记忆下。
在开发项目的过程中其实已经使用到了设计模式,但是不能自知。

比如:之前开发的基于AI的一个对话IM,里面涉及到了很多的设计模式。但是都是下意识的去使用,甚至连他是那种设计模式都有些模糊。
(ps:就单例模式记得最熟)

本篇呢,也是对之前在IM开发中,遇到的一些设计模式,进行了认真的总结。也希望读者们可以恍然大悟!!!
哎!!!! 原来我这块代码用到了XXX设计模式。
下面就依次的对我使用到的设计模式进行剖析(代码被我简化了)。

单例设计模式

模式选用说明

说明:这个非常常见的设计模式,相信大家都用过的。几乎系统中的Manager类都是通过单例实现
使用场景:对于频繁的创建、销毁的对象,并且想全局的保留这个类的状态的时候,你就给我用上!!!!!
目的: 确保一个类只有一个实例,并提供该实例的全局访问点
设计模式类别:创建型模式

场景复现:

那必须是一个网络请求的管理类

  • 需要频繁地创建和销毁对象
  • 需要全局访问
  • 保存一些登录凭证、用户信息等共享的数据,方便其他模块进行使用

咱就是说,这不是妥妥的需要用单例模式的场景吗
先说一下小编最喜欢用的几种实现(JAVA and Kotlin ps:完全是因为写着顺手并且适用于所有场景)
其他单例的实现方式,详细的请看设计模式之《单例设计模式》
Java(静态内部类的方式)

public class OkHttpManager {
    OkHttpManager(){}

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

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

Java(双重检验锁)

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(饿汉式)

object OkHttpManager { }

Kotlin(双重检验锁)

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

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

构建者设计模式

构建者模式这个有点太常见了,比较大的框架比如OkHttp,Glide都利用了构建者模式去创建对象,这样方便于使用者能够进行一定的自定义。

模式选用说明

说明:使用多个简单的对象一步一步构建成一个复杂的对象。
使用场景:当一个类的构造函数参数个数超过4个,而且这些参数有些是可选的参数,并且一些基本部件不会变的对象,就考虑使用构造者模式。
目的:解决复杂对象的构建
设计模式类别:创建型模式

场景复现

那必然是自定义的Dialog啊兄弟们,项目中需要大量的使用到Dialog,IM模块中也是必不可少的。那原声的不满足要求,但是你也不能遇到一个场景就去自定义一个Dialog啊。
我们直接自定义一个全局使用的,之后通过构建者模式去控制它的功能、样式、事件的处理不就行了
开干!!!

  • 第一步:继承Dialog并初始化
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 {
        //这部分省略了就是设置一些布局相关的,当然你可以自定义布局
    }
  • 第二步:利用静态内部类,构建链式调用设置属性
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
        )
    }
}
  • 第三步:链式调用构建对象
val dialog = CustomDialog.Builder(context)
    .setTitle("Dialog Title")
    .setContent("Dialog Content")
    .setPositiveButton("OK")
    .setNegativeButton("Cancel")
    .setCancelable(true)
    .setBackgroundColor(Color.YELLOW)
    .build()

dialog.show()

当然这只是举例子,真实项目中的利用构建者自定义的Dialog,每个内容都是支持自定义的,包括确定和取消按钮的布局样式。

工厂设计模式

先说一下工厂模式和上面的策略模式的区别,以便大家别看懵圈了,因为两种模式特别的相似。

  • 区别一:用途不一样
    • 工厂模式是创建型模式,他是创建对象的。
    • 策略是行为型模式,他是让已经创建对象去选择一个属于自己的方法行为的。
  • 区别二:使用时候不同
    • 工厂方法模式调用方可以直接调用工厂方法实例的方法属性等。
    • 策略模式不能直接调用实例的方法属性,需要在策略类中封装策略后调用。
  • 区别总结:也就是说工厂是通过创建对象来多样化,策略是通过选择行为来多样化。
  • 结合使用:即工厂模式可以用来创建多样化的策略对象。

模式选用说明

说明:这是一种很常见的创建型的设计模式,这种方式使我们的对象的创建与使用代码分离,他不会向外层暴露内部对象的创建逻辑
使用场景:设计到多种类型的对象创建,并且这多种类型都是一个业务场景的。你就给我改成工厂模式
目的:创建产品对象的工厂接口,让其子类决定实例化哪一个类,将实际创建工作推迟到子类当中
设计模式类别:创建型模式

场景复现

多种类型的Message消息,我想统一的使用HandleMessage方法。
因为对于IM消息,我肯定会涉及到获取内部的方法属性等等。所以我这里选择工厂设计模式。策略模式只会去handle我的消息而已。
原代码场景:

  • 根据不同类型的IM消息,去实例化不同的对象,去调用HandleMessage
  • 比如图片、视频、文本、语音
if (msgType = "文本") {
    TextMessageStrategy().handleMessage(messageData)
} else if(msgType = "图片") {
    ImageMessageStrategy().handleMessage(messageData)
} else if(msgType = "视频") {
    VideoMessageStrategy().handleMessage(messageData)
} else {
    DefaultMessageStrategy().handleMessage(messageData)
}

这时候你想想,当你的IM类型多了起来,这段代码是多么难受,并且如果多人去开发不同的IM消息类型,大家都要去操作这个段代码。一点解偶性都没有。
如果你想在外层操作这个代码,就要把你创建的对象抛出去。
利用工厂模式进行改进:

  • 第一步:创建统一处理handleMessage接口
interface IMessageHandle {
    //属于哪种文件解析类型 MessageTypeResolveEnum 用枚举类定义的IM类型
    fun getMessageType(): MessageTypeResolveEnum?

    //具体的处理消息方法
    fun handleMessage(messageData: BaseMessageData?)
}
  • 第二步:去统一实现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());
    }
}
  • 第三步:创建工厂
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;
        }
    }
}
  • 第四步:使用
val messageData: BaseMessageData = BaseMessageData("hahaha", MessageTypeResolveEnum.TEXT)
val message = IMessageFactory.parse(MessageTypeResolveEnum.TEXT.type)
IMAdapter.addItem(message)
message?.getMessageType()
message?.handleMessage(messageData)

策略设计模式

策略模式用的肯定是要比工厂模式少一些的,因为多策略的场景没那么多。
最常见的几种:不同会员等级对商品进行打折处理、多文件类型的处理、日志的多种类控制台输出日志、文件记录日志、数据库记录日志等。
其实这里对会员的策略讲解起来比较清晰,但是本篇文章是总结我开发的IM的,所以就说说对文件类型的不同策略处理吧。

模式选用说明

说明:我想使得一个类的行为或其算法可以在运行时更改。
使用场景:一个系统有许多许多类,而区分它们的只是他们直接的行为(如果一个系统的策略多于四个,就需要考虑使用混合模式,解决策略类膨胀的问题)
目的:在有多种算法相似的情况下,解决大量的if else 的判断
设计模式类别:行为型模式

这里再次强调和工厂设计模式的区别,因为上面的IM消息我会涉及到内部的消息属性,所以我使用了工厂设计模式。
那什么场景下用策略设计模式呢?
答:只涉及到,你想区分这个类,仅仅是他的行为不同。

场景复现

比如我现在有几类的文件消息:表格、PDF、WORD、流程图等等多种类文件。那么他们如果在展示过程中都是相同的,都在消息中显示为文件。但是我点击后的打开方式不同。 那么我们还要去创建多种文件对象?他只有打开方式不同,其他都相同。
那么这个时候是不是就符合了我们的策略模式解决的问题,他只是打开的行为不同。
原代码

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

如果还有区分会员非会员,还有会员等级等等。那么这不是就多起来了吗,那怎么办?
利用策略设计模式进行改进:

  • 第一步:定义统一策略接口
interface FileMessageStrategy {
    //属于哪种文件解析类型 FileMessageTypeEnum 用枚举类定义的IM类型
    fun getFileType(): FileMessageTypeEnum?

    //封装的公用算法(具体的打开方法)
    fun openFile(messageData: BaseMessageData?)
}
  • 第二步:不同消息的处理策略实现
// 表格文件消息
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())
    }
}
  • 第三步:将文件类型和相关处理进行绑定
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)

装饰者设计模式

简单的认为,他就是扩展一个类的功能。那么有人问了,你扩展功能,用继承不就行了吗?
可以增加方法,重写方法。 那确实是这么回事,继承确实可以去实现你要扩展功能的要求。
但是你会遇到下面几个问题:

  • 强依赖、强固定的问题
    • 当你不断的有新的功能进行扩展,你就要不断的去往下继承子类吗? 你想想到时候那一串子的子类,到时候维护和查看的难度,看了想死。
    • 当你再后期的维护中有想,移除某个中间迭代的功能,这时候因为你是通过继承不断的传递下来了,这改一下子,难受的要死,甚至对其他功能还会有影响。
  • 单继承问题
    • 万一你要扩展的这个类,还要继承其他的类,比如一个抽象的功能类。咋办?
    • 装饰者就可以规避这个问题
  • 运行时的动态添加
    • 当你选用继承关系的时候,你当前的功能就在编译时被固定了,运行时无法限制。
    • 当你选用装饰者设计模式的时候,你可以在运行动态的对要扩展的功能进行添加
      上面就是为什么要选择装饰者模式,而不用继承的原因了。

模式选用说明

说明:加强版的继承扩展类功能的方式,比如Android中的RecyclerView.Adapter。
使用场景:扩展一个类的功能,并且后续还有扩展功能的需求。或者不能用继承去扩展这个类的功能的时候。
目的:动态的扩展一个类的功能,并且不想添加大量子类。
设计模式类别:结构型模式

场景复现

就是说我现在有一个订单功能,其中有一个基础的订单类Order。有以下几个常用的方法

  • 获取订单ID
  • 获取订单金额
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);
    }
}

我现在想扩展两个功能,分了两次需求来的。那么我不想通过继承,而是装饰者去扩展。

  • 第一步:创建基础的装饰者类
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();
    }
}
  • 第二步:扩展第一个功能,订单日志记录
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());
    }
}
  • 扩展第二个内容:发送邮件通知
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());
    }
}
  • 第三步:使用,可以动态的切换两个功能去使用
Order order = new Order("12345", 100.0);
Order decoratedOrder;
if (version == "4.0"){
    decoratedOrder = new LoggedOrder(order);
} else {
    decoratedOrder = new NotificationOrder(order);
}
decoratedOrder.process();

看到没,通过使用装饰者设计模式,我们可以动态地为订单对象添加不同的功能,而无需修改订单类或创建大量的子类。极大的增加了可扩展性和灵活性。

适配器设计模式

适配器模式其实大家最常见了,那就是我们常用的列表RecyclerView,他的Adapter适配器。
适配器模式旨在:使得原本由于接口不兼容而不能一起工作的那些类可以一起工作。
对于RecyclerView你可以这么去想:你的数据是现有的系统、你要把数据转换成View,View就是你的厂商类,那么这个时候Adapter就是适配器。也就是GetView这个函数去将不同的数据和当前View进行绑定。
这个图太形象了。
一篇文章搞定《实战中的设计模式之Android版》_第1张图片

模式选用说明

说明:使得原本由于接口不兼容而不能一起工作的那些类可以一起工作。也就是我们所说的中间层,也就是接口不兼容的情况。
使用场景:需要一个统一的输出接口,而输入端的接口不可预知
目的:适配接口,让不可预测的输入端,统一的输出。
设计模式类别:结构型模式

场景复现

1、我们正在开发一个支付系统,我们需要适配两个新加入的第三方支付方式
2、但是我们最后只想通过之前的接口去统一支付
3、这时候我们可以创建一个适配器类,把这两第三方的支付接口,适配到之前原有的支付接口上去
大家说我用if else 也可以实现。 那确实,那代码看起来就像粑粑了,而且随着你支付方式的增多,当中途有支付方式的修改,增加,减少就会越来越变得难以维护。这个就叫做耦合。
来看看适配器怎么做吧。

  • 之前统一的支付接口
public interface Payment {
    void pay(float amount);
}
  • 新加入的两个三方支付接口:比如是微信、支付宝
//微信
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);
    }
}
  • 第一步:建立适配器类PaymentAdapter,用于适配所有支付方式:(这种是类适配器的方式)
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);
        }
        // 其他支付方式的调用...
    }
}
  • 第二步:使用适配器
//阿里支付
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);

当然知识去演示了一下,如何使用这个适配器,真实环境下。你的payment.pay是在主类里的。肯定不会去动。
而各种支付方式就会变成入参进来。
所以需要统一接口去用,你通过适配器将入参传递进来就可以。这样就避免了去修改支付主类。

总结

一切的设计模式,都是为了解决耦合性、扩展性、复用性、可测试性。
它并不是,为了解决一些代码问题。所有的需要通过任何方式都可以实现,并不是必须使用设计模式。
但是!!!!
设计模式在较大项目中,就能体现出来他的优势,耦合性、扩展性、复用性、可测试性让后续的维护和开发大大减少了时间。
减少的时间在于:不用担心牵一发动全身(耦合性)、你不用关心内部实现(扩展性)、拿来就用(复用性)、快速的单元测试(可测试性)。

你可能感兴趣的:(设计模式,一篇文章搞定Android,Android,设计模式,android)