设计模式(1)

xiaoxiao2021-02-28  40

1 简介

设计模式是一套被反复使用、多数人知晓的、经过分类的、代码设计经验的总结。使用设计模式的目的是为了提高代码的可重用性、可读性和可靠性。

设计模式代表了最佳的实践,通常被有经验的软件开发人员所采用,可以解决很多软件开发过程所面临的问题。设计模式是众多软件开发人员在长时间大量的实践中总结出来的。

使用设计模式的根本原因是为了代码复用,增加可维护性。设计模式使用最广泛的地方是在框架中。因为框架的目的是为了设计复用(将通用的处理抽取出来,以便开发者只需要关注业务本身),所以框架设计中必然要使用设计模式。

设计模式很好地体现了面向对象的设计原则。

2 具体模式

设计模式一共有23种,根据其目的可以分为三类:创建型模式,结构型模式,行为型模式。

创建型模式包括:单例模式、抽象工厂模式、建造者模式、工厂模式、原型模式。 结构型模式包括:适配器模式、桥接模式、装饰模式、组合模式、外观模式、享元模式、代理模式。 行为型模式包括:模板方法模式、命令模式、迭代器模式、观察者模式、中介者模式、备忘录模式、解释器模式、状态模式、策略模式、责任链模式、访问者模式。

设计模式数量较多,概念比较抽象,只有深入理解了才能掌握好。其中很多常见的模式如单例模式,工厂模式,适配器模式,桥接模式等在日常开发中可能比较常见。下面将根据类型分别进行介绍。

2.1 创建型模式

2.1.1 单例模式

单例模式是个值得认真研究的模式。把单例模式中饿汉模式和懒汉模式的不同点,懒汉模式存在的问题,双重检查锁,双重检查锁存在的问题,静态内部类实现懒汉模式都研究清楚了就基本掌握单例模式了。这里就不拓展开来讲了,具体可见参考资料[1]。

2.1.2 工厂模式和抽象工厂模式

简单工厂

先说说简单工厂。简单工厂其实并不是一个设计模式,更是一种编程习惯。为了方便理解,先从简单工厂说起。 先定义一个工厂类,其中有一个生产水果的方法creatFruit(String type),该方法根据参数type来创建不同的水果。在客户端中调用该方法,传入不同的参数就可以得到不同的水果。这个工厂就是一个简单工厂。工厂方法逻辑为:

If a then fruitA; If b then fruitB;

现在工厂类和生产水果的方法定义好了,可以根据不同种类生产A和B两种水果了。假如现在需要多产生一种水果C,那怎么办呢?需要修改生产水果的方法,变成:

If a then fruitA; If b then fruitB; If c then fruitC;

每次需要新增水果,就需要修改原生产方法。这样做违反了面向对象的一个设计原则:开闭原则。该原则强调的是面向拓展开放,面向修改封闭。实际上这也是面向对象软件设计的终极原则,其他原则都或多或少的都在为这一目标而努力。

工厂方法模式

工厂方法模式可以解决这一问题。首先创建一个工厂接口和一个水果接口,然后A水果工厂和B水果工厂都实现工厂接口。A水果和B水果都实现水果接口。要生产A水果就通过A工厂生产,要生产B水果就通过B工厂生产。

If a CallFactoryMehtodA; then fruitA; If b CallFactoryMethodB; then fruitB;

要生产新的水果C,只需要新增一个工厂类C来实现工厂接口,实现C工厂方法来生产水果C,而不需要修改已有的工厂方法了。后面需要生产新的水果都只需要新增自己的工厂类来实现工厂接口就行了。

抽象工厂模式

工厂模式中水果工厂生产的水果物美价廉,畅销全球,赚了很多钱。但是鸡蛋不能放在一个篮子里,为了分摊风险和创造新的盈利点,需要扩展其他业务了。于是A水果工厂不再像以前一样只生产Apple苹果了,还生产Apple手机了。基于已有的思路,我们可以仍然用工厂模式来实现,先定义一个工厂接口,然后新建一个工厂类来实现这个接口,实现接口的工厂方法来生产Apple手机。做起来相当于是将生产水果的工厂模式复制了一份,很明显这其中有很多重复工作。这时候就可以使用抽象工厂模式了。

原来apple工厂只有一个工厂方法,用来生产apple水果的,现在我们新增一个工厂方法,该方法用来生产apple手机。这样一来,就只需要新建一个方法就可以实现目的,大大减少了重复的工作量。依次类推,我们需要生产apple手表,apple电脑等等,都只需要在apple工厂里面新建对应的工厂方法就可以了。当然,新的产品类也要有新的产品接口和实现类。

总结 整体来看,工厂模式是在简单工厂的基础上进行横向扩展,即先定义接口,然后实现不同的工厂类(工厂由一个变成一类的多个)。而抽象工厂模式则是在工厂模式的基础上进行纵向扩展,即在接口里面新增工厂方法,然后在对应的工厂实现类里面实现新的工厂方法生产新的产品(产品由一类变成多类)。

值得一提的是,抽象工厂模式要做好模型分类,因为工厂的接口方法需要在所有的工厂实现类里面实现,在工厂接口里面添加生产手机,电脑的工厂方法之后,如果有其他的工厂类实现了这个接口,同样要实现这些方法。也就是说,实现同一接口的工厂,必须具有相同的逻辑意义的类别。比如上文中扩展后的apple(品牌)和orange(水果)并不是一个类别,使用抽象工厂模式后,orange工厂也要实现产生手机和生产电脑的工厂方法了,这样就不合适了。假如把orange替换成xiaomi,那使用抽象工厂模式就是合适的。

用一句话来总结一下,那就是:简单工厂是一个工厂生产一类的产品;工厂方法模式是一类工厂(多个)生产一类产品;抽象工厂模式是一类工厂(多个)生产多类产品。这样总结的话应该是比较清晰了。类图这里就不给出了。

2.1.3 建造者模式

创建型模式中,与单例模式和工厂模式、抽象工厂模式相比,建造者模式使用就没那么广泛了。所以建造者模式虽说理解起来也比较简单,但容易忘记。例如博主自己就经常忘记,需要看一眼才能想起来。

建造者模式就从定义说起:将一个复杂对象的构建和它的表示分离,使得同样的建造过程可以创建不同的表示。

可以说,建造者模式的精髓就全在这句话里面了。 首先建造者模式创建的是复杂对象,这个对象里面,可能含有多个Object类型的属性。然后这个模式里面有两种角色,一种角色负责对象的构建(复杂对象的整体组装),一种负责细节表示(复杂对象的属性的具体生成)。为什么需要两种角色来将对象的构建和表示分离呢?目的就是使得同样的建造过程可以创建不同的表示。从这句话可以看出,建造过程是固定的,细节表示是不同的。因此将固定的东西抽取出来由一个角色来处理,可变的东西则交由另外一个角色来进行处理,而且这个角色的实现者必须能够扩展。其实这里的基本思想就是将不变的部分和可变的部分进行分离,这是编程的基本思想。 可变的部分由不同的建造者进行表示,这里实际上用到的就是上文的工厂模式的思想了。先定义一个建造者接口,然后不同的建造者都实现这个接口,并实现接口的方法。每个建造者实现接口方法的具体逻辑不同,因此每个建造者产生的细节表示是不同的。

下面用一个例子来表示,假设去麦当劳点套餐吃。一个套餐固定由汉堡,薯条和饮料组成。但是每个具体的部分是可变的,比如汉堡有鸡肉汉堡和三明治汉堡,饮料有可乐有橙汁等。根据建造者模式的定义,需要两个角色:Director来负责组成一个套餐(复杂对象),Builder负责生成套餐的具体表示。假设有三种套餐,则需要三个具体建造者,分别是BuilderA,BuilderB,BuilderC,均实现Builder接口。 Builder接口:

public interface Builder { void createHamburger(); void createChip(); void createDrink(); SetMenu getSetMenu(); }

A套餐建造者BuilderA:

public class BuilderA implements Builder { private SetMenu setMenu; @Override public void createHamburger() { setMenu.setHamburger("三明治汉堡"); } @Override public void createChip() { setMenu.setChip("大份薯条"); } @Override public void createDrink() { setMenu.setDrink("可乐"); } @Override public SetMenu getSetMenu() { return setMenu; } public void setSetMenu(SetMenu setMenu) { this.setMenu = setMenu; } }

BuilderB和BuilderC类似,只是每个套餐细节的表示不同。 复杂对象(套餐):

public class SetMenu { private Object hamburger; private Object chip; private Object drink; public Object getHamburger() { return hamburger; } public void setHamburger(Object hamburger) { this.hamburger = hamburger; } public Object getChip() { return chip; } public void setChip(Object chip) { this.chip = chip; } public Object getDrink() { return drink; } public void setDrink(Object drink) { this.drink = drink; } }

Director指导者:

public class Director { public SetMenu constructSetMenu(Builder builder) { builder.createHamburger(); builder.createChip(); builder.createDrink(); return builder.getSetMenu(); } }

客户端主函数调用如下:

public class Client { public static void main(String[] args) { Director director = new Director(); BuilderA builderA = new BuilderA(); SetMenu setMenu = new SetMenu(); builderA.setSetMenu(setMenu); SetMenu outputSetMenu = director.constructSetMenu(builderA); System.out.println(outputSetMenu.getHamburger() + "," + outputSetMenu.getChip() + "," +outputSetMenu.getDrink()); } }

类图如下: 类图来自参考资料3。

2.1.4 原型模式

创建型模式都跟对象的创建有关,除了单例模式,工厂/抽象工厂模式,建造者模式之外,原型模式也是其中一种。我们知道,对象有几种生成方式,如通过构造函数生成;通过Class.forName(xxx).newInstance()生成;通过反射生成;通过clone()方法进行复制。而这最后一种通过clone生成对象的方式正是原型模式的应用。

所谓原型模式,就是java中的克隆技术,以某个对象为原型,复制出新的对象。 克隆出来的对象的属性值完全跟原型对象相同。要实现原型模式,必须实现Cloneable接口,并重写clone方法:

@Override protected Object clone() throws CloneNotSupportedException { return super.clone(); }

值得注意的是,克隆分为两种:浅克隆和深克隆。如果被克隆的对象中含有引用变量,则浅克隆只会克隆这个引用,而不会克隆引用所指向的对象。例如上面调用super.clone即Object的克隆方法进行的克隆就是浅克隆。要实现深克隆必须手动来对每一个复杂属性进行深克隆,如果复杂属性中继续嵌套着其他复杂属性,要实现深克隆是比较麻烦的。

下一篇中将继续介绍结构型设计模式。

参考资料

1.https://blog.csdn.net/jslcylcy/article/details/72722806 2.https://www.cnblogs.com/LUO77/p/5785906.html 3.https://blog.csdn.net/u013256816/article/details/50978024

转载请注明原文地址: https://www.6miu.com/read-2450024.html

最新回复(0)