其实大多数可能和我一样,在开发项目的累积经验下,和对设计模式隐约的记忆下。
在开发项目的过程中其实已经使用到了设计模式,但是不能自知。
比如:之前开发的基于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啊。
我们直接自定义一个全局使用的,之后通过构建者模式去控制它的功能、样式、事件的处理不就行了
开干!!!
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我的消息而已。
原代码场景:
if (msgType = "文本") {
TextMessageStrategy().handleMessage(messageData)
} else if(msgType = "图片") {
ImageMessageStrategy().handleMessage(messageData)
} else if(msgType = "视频") {
VideoMessageStrategy().handleMessage(messageData)
} else {
DefaultMessageStrategy().handleMessage(messageData)
}
这时候你想想,当你的IM类型多了起来,这段代码是多么难受,并且如果多人去开发不同的IM消息类型,大家都要去操作这个段代码。一点解偶性都没有。
如果你想在外层操作这个代码,就要把你创建的对象抛出去。
利用工厂模式进行改进:
interface IMessageHandle {
//属于哪种文件解析类型 MessageTypeResolveEnum 用枚举类定义的IM类型
fun getMessageType(): MessageTypeResolveEnum?
//具体的处理消息方法
fun handleMessage(messageData: BaseMessageData?)
}
// 文本消息
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。有以下几个常用的方法
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进行绑定。
这个图太形象了。
说明:使得原本由于接口不兼容而不能一起工作的那些类可以一起工作。也就是我们所说的中间层,也就是接口不兼容的情况。
使用场景:需要一个统一的输出接口,而输入端的接口不可预知
目的:适配接口,让不可预测的输入端,统一的输出。
设计模式类别:结构型模式
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);
}
}
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是在主类里的。肯定不会去动。
而各种支付方式就会变成入参进来。
所以需要统一接口去用,你通过适配器将入参传递进来就可以。这样就避免了去修改支付主类。
一切的设计模式,都是为了解决耦合性、扩展性、复用性、可测试性。
它并不是,为了解决一些代码问题。所有的需要通过任何方式都可以实现,并不是必须使用设计模式。
但是!!!!
设计模式在较大项目中,就能体现出来他的优势,耦合性、扩展性、复用性、可测试性让后续的维护和开发大大减少了时间。
减少的时间在于:不用担心牵一发动全身(耦合性)、你不用关心内部实现(扩展性)、拿来就用(复用性)、快速的单元测试(可测试性)。