一、软件设计模式的几种分类:
1.1. 创建型
创建对象时,不再由我们直接实例化对象;而是根据特定场景,由程序来确定创建对象的方式,从而保证更大的性能、更好的架构优势。创建型模式主要有简单工厂模式
(并不是23种设计模式之一)、工厂方法、抽象工厂模式、单例模式、生成器模式和原型模式。
<>1.2. 结构型
用于帮助将多个对象组织成更大的结构。结构型模式主要有适配器模式adapter、桥接模式bridge
、组合器模式component、装饰器模式decorator、门面模式、亨元模式flyweight和代理模式proxy。
<>1.3. 行为型
用于帮助系统间各对象的通信,以及如何控制复杂系统中流程。行为型模式主要有命令模式command、解释器模式、迭代器模式、中介者模式、备忘录模式、观察者模式
、状态模式state、策略模式、模板模式和访问者模式。
二、常用的设计模式介绍
2.1. 单例模式(singleton)
有些时候,允许自由创建某个类的实例没有意义,还可能造成系统性能下降。如果一个类始终只能创建一个实例,则这个类被称为单例类,这种模式就被称为单例模式。
一般建议单例模式的方法命名为:getInstance(),这个方法的返回类型肯定是单例类的类型了。getInstance方法可以有参数,这些参数可能是创建类实例所需要的参数,当然,大多数情况下是不需要的。
public class Singleton { private static Singleton singleton; private
Singleton() { } public static Singleton getInstance() { if (singleton == null)
{ singleton = new Singleton(); } return singleton; } }
优点:
1.在单例模式中,活动的单例只有一个实例,对单例类的所有实例化得到的都是相同的一个实例。这样就
防止其它对象对自己的实例化,确保所有的对象都访问一个实例
2.单例模式具有一定的伸缩性,类自己来控制实例化进程,类就在改变实例化进程上有相应的伸缩性。
3.提供了对唯一实例的受控访问。
4.由于在系统内存中只存在一个对象,因此可以 节约系统资源,当 需要频繁创建和销毁的对象时单例模式无疑可以提高系统的性能。
5.允许可变数目的实例。
6.避免对共享资源的多重占用。
缺点:
1.不适用于变化的对象,如果同一类型的对象总是要在不同的用例场景发生变化,单例就会引起数据的错误,不能保存彼此的状态。
2.由于单利模式中没有抽象层,因此单例类的扩展有很大的困难。
3.单例类的职责过重,在一定程度上违背了“单一职责原则”。
4.滥用单例将带来一些负面问题,如为了节省资源将数据库连接池对象设计为的单例类,可能会导致共享连接池对象的程序过多而出现连接池溢出;如果实例化的对象长时间不被利用,系统会认为是垃圾而被回收,这将导致对象状态的丢失。
使用注意事项:
1.使用时不能用反射模式创建单例,否则会实例化一个新的对象
2.使用懒单例模式时注意线程安全问题
3.单例模式和懒单例模式构造方法都是私有的,因而是不能被继承的,有些单例模式可以被继承(如登记式模式)
适用场景:
单例模式只允许创建一个对象,因此节省内存,加快对象访问速度,因此对象需要被公用的场合适合使用,如多个模块使用同一个数据源连接对象等等。如:
1.需要频繁实例化然后销毁的对象。
2.创建对象时耗时过多或者耗资源过多,但又经常用到的对象。
3.有状态的工具类对象。
4.频繁访问数据库或文件的对象。
以下都是单例模式的经典使用场景:
1.资源共享的情况下,避免由于资源操作时导致的性能或损耗等。如上述中的日志文件,应用配置。
2.控制资源的情况下,方便资源之间的互相通信。如线程池等。
应用场景举例:
1.外部资源:每台计算机有若干个打印机,但只能有一个PrinterSpooler,以避免两个打印作业同时输出到打印机。内部资源:大多数软件都有一个(或多个)属性文件存放系统配置,这样的系统应该有一个对象管理这些属性文件
2. Windows的TaskManager(任务管理器)就是很典型的单例模式(这个很熟悉吧),想想看,是不是呢,你能打开两个windows
task manager吗? 不信你自己试试看哦~
3. windows的Recycle Bin(回收站)也是典型的单例应用。在整个系统运行过程中,回收站一直维护着仅有的一个实例。
4. 网站的计数器,一般也是采用单例模式实现,否则难以同步。
5. 应用程序的日志应用,一般都何用单例模式实现,这一般是由于共享的日志文件一直处于打开状态,因为只能有一个实例去操作,否则内容不好追加。
6. Web应用的配置对象的读取,一般也应用单例模式,这个是由于配置文件是共享的资源。
7.
数据库连接池的设计一般也是采用单例模式,因为数据库连接是一种数据库资源。数据库软件系统中使用数据库连接池,主要是节省打开或者关闭数据库连接所引起的效率损耗,这种效率上的损耗还是非常昂贵的,因为何用单例模式来维护,就可以大大降低这种损耗。
8. 多线程的线程池的设计一般也是采用单例模式,这是由于线程池要方便对池中的线程进行控制。
9. 操作系统的文件系统,也是大的单例模式实现的具体例子,一个操作系统只能有一个文件系统。
10. HttpApplication
也是单位例的典型应用。熟悉ASP.Net(IIS)的整个请求生命周期的人应该知道HttpApplication也是单例模式,所有的HttpModule都共享一个HttpApplication实例.
2.2. 观察者模式(Observer)
对象间一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新。Android中的各种Listener就使用到了这一设计模式,只要用户对手机进行操作,对应的listener就会被通知,并作出响应的处理。
看不懂图的人端着小板凳到这里来,给你举个栗子��:假设有三个人,小美(女,28),老王和老李。小美很漂亮,很风骚,老王和老李是两个中年男屌丝,时刻关注着小美的一举一动。有一天,小美说了一句:我老公今天不在家,一个人好无聊啊~~~,这句话被老王和老李听到了,结果乐坏了,蹭蹭蹭,没一会儿,老王就冲到小美家门口了,于是进门了………
在这里,小美是被观察者,老王和老李是观察者,被观察者发出一条信息,然后被观察者进行相应的处理,看代码:
public interface Person { //老王和老李通过这个接口可以接收到小美发过来的消息 void getMessage(String
s); }
这个接口相当于老王和老李的电话号码,小美发送通知的时候就会拨打getMessage这个电话,拨打电话就是调用接口,看不懂没关系,先往下看
public class LaoWang implements Person { private String name = "老王"; public
LaoWang() { } @Override public void getMessage(String s) {
System.out.println(name + "接到了小美打过来的电话,电话内容是:" + s); } } public class LaoLi
implements Person { private String name = "老李"; public LaoLi() { } @Override
public void getMessage(String s) { System.out.println(name +
"接到了小美打过来的电话,电话内容是:->" + s); } }
代码很简单,我们再看看小美的代码:
public class XiaoMei { List<Person> list = new ArrayList<Person>(); public
XiaoMei(){ } public void addPerson(Person person){ list.add(person); }
//遍历list,把自己的通知发送给所有暗恋自己的人 public void notifyPerson() { for(Person
person:list){ person.getMessage("今天家里就我一个人,你们过来吧,谁先过来谁就能得到我!"); } } }
我们写一个测试类来看一下结果对不对
public class Test { public static void main(String[] args) { XiaoMei xiao_mei
= new XiaoMei(); LaoWang lao_wang = new LaoWang(); LaoLi lao_li = new LaoLi();
//老王和老李在小美那里都注册了一下 xiao_mei.addPerson(lao_wang); xiao_mei.addPerson(lao_li);
//小美向老王和老李发送通知 xiao_mei.notifyPerson(); } }
运行结果我截图了
完美~~~
2.3. 装饰者模式 (Decorator Pattern)
对已有的业务逻辑进一步的封装,使其增加额外的功能,如java中的IO流就使用了装饰者模式,用户在使用的时候,可以任意组装,达到自己想要的效果。
举个栗子,我想吃三明治,首先我需要一根大大的香肠,我喜欢吃奶油,在香肠上面加一点奶油,再放一点蔬菜,最后再用两片面包加一下,很丰盛的一顿午饭,营养又健康,那我们应该怎么来写代码呢?
首先,我们需要写一个Food类,让其他所有食物都来继承这个类,看代码:
public class Food { private String food_name; public Food() { } public
Food(String food_name) { this.food_name = food_name; } public String make() {
return food_name; }; }
代码很简单,我就不解释了,然后我们写几个子类继承它:
//面包类 public class Bread extends Food { private Food basic_food; public
Bread(Food basic_food) { this.basic_food = basic_food; } public String make() {
return basic_food.make()+"+面包"; } } //奶油类 public class Cream extends Food {
private Food basic_food; public Cream(Food basic_food) { this.basic_food =
basic_food; } public String make() { return basic_food.make()+"+奶油"; } } //蔬菜类
public class Vegetable extends Food { private Food basic_food; public
Vegetable(Food basic_food) { this.basic_food = basic_food; } public String
make() { return basic_food.make()+"+蔬菜"; } }
这几个类都是差不多的,构造方法传入一个Food类型的参数,然后在make方法中加入一些自己的逻辑,如果你还是看不懂为什么这么写,不急,你看看我的Test类是怎么写的,一看你就明白了
public class Test { public static void main(String[] args) { Food food = new
Bread(new Vegetable(new Cream(new Food("香肠"))));
System.out.println(food.make()); } }
看到没有,一层一层封装,我没从里往外看:最里面我new了一个香肠,在香肠的外面我包裹了一层奶油,在奶油的外面我又加了一层蔬菜,最外面我放的是面包,是不是很形象,哈哈
~ 这个设计模式简直跟现实生活中一摸一样,看懂了吗?
我们看看运行结果吧
一个三明治就做好了~~~
应用实例:
1、孙悟空有 72 变,当他变成"庙宇"后,他的根本还是一只猴子,但是他又有了庙宇的功能。
2、不论一幅画有没有画框都可以挂在墙上,但是通常都是有画框的,并且实际上是画框被挂在墙上。在挂在墙上之前,画可以被蒙上玻璃,装到框子里;这时画、玻璃和画框形成了一个物体。
优点:装饰类和被装饰类可以独立发展,不会相互耦合,装饰模式是继承的一个替代模式,装饰模式可以动态扩展一个实现类的功能。
缺点:多层装饰比较复杂。
适用环境:
(1)在不影响其他对象的情况下,以动态、透明的方式给单个对象添加职责。
(2)处理那些可以撤消的职责。
(3)当不能采用生成子类的方法进行扩充时。一种情况是,可能有大量独立的扩展,为支持每一种组合将产生大量的
子类,使得子类数目呈爆炸性增长。另一种情况可能是因为类定义被隐藏,或类定义不能用于生成子类。
2.4. 适配器模式
适配器模式(Adapter Pattern)是作为两个不兼容的接口之间的桥梁。这种类型的设计模式属于结构型模式,它结合了两个独立接口的功能。
这种模式涉及到一个单一的类,该类负责加入独立的或不兼容的接口功能。举个真实的例子,读卡器是作为内存卡和笔记本之间的适配器。您将内存卡插入读卡器,再将读卡器插入笔记本,这样就可以通过笔记本来读取内存卡。
假设一个手机充电器需要的电压是20V,但是正常的电压是220V,这时候就需要一个变压器,将220V的电压转换成20V的电压,这样,变压器就将20V的电压和手机联系起来了。
public class Test { public static void main(String[] args) { Phone phone = new
Phone(); VoltageAdapter adapter = new VoltageAdapter();
phone.setAdapter(adapter); phone.charge(); } } // 手机类 class Phone { public
static final int V = 220;// 正常电压220v,是一个常量 private VoltageAdapter adapter; //
充电 public void charge() { adapter.changeVoltage(); } public void
setAdapter(VoltageAdapter adapter) { this.adapter = adapter; } } // 变压器 class
VoltageAdapter { // 改变电压的功能 public void changeVoltage() {
System.out.println("正在充电..."); System.out.println("原始电压:" + Phone.V + "V");
System.out.println("经过变压器转换之后的电压:" + (Phone.V - 200) + "V"); } }
适配器模式优点: 1、可以让任何两个没有关联的类一起运行。 2、提高了类的复用。 3、增加了类的透明度。 4、灵活性好。
缺点: 1、过多地使用适配器,会让系统非常零乱,不易整体进行把握。比如,明明看到调用的是 A 接口,其实内部被适配成了 B
接口的实现,一个系统如果太多出现这种情况,无异于一场灾难。因此如果不是很有必要,可以不使用适配器,而是直接对系统进行重构。 2.由于 JAVA
至多继承一个类,所以至多只能适配一个适配者类,而且目标类必须是抽象类。
使用场景:有动机地修改一个正常运行的系统的接口,这时应该考虑使用适配器模式。
注意事项:适配器不是在详细设计时添加的,而是解决正在服役的项目的问题。
2.5. 工厂模式 (Factory Pattern)
就是建立一个工厂类,对实现了同一接口的一些类进行实例的创建。简单工厂模式的实质是由一个工厂类根据传入的参数,动态决定应该创建哪一个产品类(这些产品类继承自一个父类或接口)的实例。
实现方式:
a) 抽象产品类(也可以是接口)
b) 多个具体的产品类
c) 工厂类(包括创建a的实例的方法)
简单工厂模式:一个抽象的接口,多个抽象接口的实现类,一个工厂类,用来实例化抽象的接口
// 抽象产品类 abstract class Car { public void run(); public void stop(); } //
具体实现类 class Benz implements Car { public void run() {
System.out.println("Benz开始启动了。。。。。"); } public void stop() {
System.out.println("Benz停车了。。。。。"); } } class Ford implements Car { public void
run() { System.out.println("Ford开始启动了。。。"); } public void stop() {
System.out.println("Ford停车了。。。。"); } } // 工厂类 class Factory { public static Car
getCarInstance(String type) { Car c = null; if ("Benz".equals(type)) { c = new
Benz(); } if ("Ford".equals(type)) { c = new Ford(); } return c; } } public
class Test { public static void main(String[] args) { Car c =
Factory.getCarInstance("Benz"); if (c != null) { c.run(); c.stop(); } else {
System.out.println("造不了这种汽车。。。"); } } }
工厂方法模式:有四个角色,抽象工厂模式,具体工厂模式,抽象产品模式,具体产品模式。不再是由一个工厂类去实例化具体的产品,而是由抽象工厂的子类去实例化产品
// 抽象产品角色 public interface Moveable { void run(); } // 具体产品角色 public class
Plane implements Moveable { @Override public void run() {
System.out.println("plane...."); } } public class Broom implements Moveable {
@Override public void run() { System.out.println("broom....."); } } // 抽象工厂
public abstract class VehicleFactory { abstract Moveable create(); } // 具体工厂
public class PlaneFactory extends VehicleFactory { public Moveable create() {
return new Plane(); } } public class BroomFactory extends VehicleFactory {
public Moveable create() { return new Broom(); } } // 测试类 public class Test {
public static void main(String[] args) { VehicleFactory factory = new
BroomFactory(); Moveable m = factory.create(); m.run(); } }
优点:
工厂类是整个模式的关键.包含了必要的逻辑判断,根据外界给定的信息,决定究竟应该创建哪个具体类的对象.通过使用工厂类,外界可以从直接创建具体产品对象的尴尬局面摆脱出来,仅仅需要负责“消费”对象就可以了。而不必管这些对象究竟如何创建及如何组织的.明确了各自的职责和权利,有利于整个
软件体系结构 <http://baike.baidu.com/view/1317046.htm>的优化。
缺点:
由于工厂类集中了所有实例的创建逻辑,违反了高内聚 <http://baike.baidu.com/view/292136.htm>
责任分配原则,将全部创建逻辑集中到了一个工厂类中;它所能创建的类只能是事先考虑到的,如果需要添加新的类,则就需要改变工厂类了。当系统中的具体产品类不断增多时候,可能会出现要求工厂类根据不同条件创建不同实例的需求.这种对条件的判断和对具体产品类型的判断交错在一起,很难避免模块功能的蔓延,对系统的维护和扩展非常不利;
2.6. 抽象工厂模式 (Abstract Factory Pattern)
与工厂方法模式不同的是,工厂方法模式中的工厂只生产单一的产品,而抽象工厂模式中的工厂生产多个产品
/抽象工厂类 public abstract class AbstractFactory { public abstract Vehicle
createVehicle(); public abstract Weapon createWeapon(); public abstract Food
createFood(); } //具体工厂类,其中Food,Vehicle,Weapon是抽象类, public class DefaultFactory
extends AbstractFactory{ @Override public Food createFood() { return new
Apple(); } @Override public Vehicle createVehicle() { return new Car(); }
@Override public Weapon createWeapon() { return new AK47(); } } //测试类 public
class Test { public static void main(String[] args) { AbstractFactory f = new
DefaultFactory(); Vehicle v = f.createVehicle(); v.run(); Weapon w =
f.createWeapon(); w.shoot(); Food a = f.createFood(); a.printName(); } }
持续更新中。。。。。。
参考链接:
几种常用的设计模式介绍 <https://blog.csdn.net/XSL1990/article/details/16359289#commentBox>
面试必备:常用的设计模式总结
<https://blog.csdn.net/Wu_Ming0821/article/details/51838078#commentBox>
十种常用的设计模式(大部分自己总结,部分摘抄)
<https://blog.csdn.net/dean_hu/article/details/71195133#t4>
热门工具 换一换