一、设计模式的本质的理解
设计模式是针对软件开发过程中反复发生的一系列经典问题的解决方法的经验总结。本质上是对于面向对象设计原则的实际应用,是对于类的封装性、继承性和多态性以及类的关联关系组合关系的充分理解。
在这种认识下,学习设计模式最重要的两点就是:
- 理解应用场景。即模式解决哪类问题。
- 实际操作代码
二、设计模式分类
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-vmAKT4tS-1589535477958)(en-resource://database/1114:1)]
本文实现:单例模式,原型模式,工厂模式和观察者模式
三、单例模式
1.场景
单例(Singleton)模式的定义:指一个类只有一个实例,且该类能自行创建这个实例的一种模式。例如,Windows 中只能打开一个任务管理器,这样可以避免因打开多个任务管理器窗口而造成内存资源的浪费,或出现各个窗口显示内容的不一致等错误。
实际应用场景中,还有 Windows 的回收站、操作系统中的文件系统、多线程中的线程池、显卡的驱动程序对象、打印机的后台处理服务、应用程序的日志对象、数据库的连接池、网站的计数器、Web 应用的配置对象、应用程序中的对话框、系统中的缓存等常常被设计成单例。
2.Lazy方法实现
lazy方式实现单例模式,将类中的构造函数私有,然后创建一个共有的get函数调用构造函数。在get函数中加入判断实例是否唯一的逻辑。
重要缺点为:多线程编程中会产生线程安全问题,需要volatile 和 synchronized关键词解决。但这两个关键字,每次访问时都要同步,会影响性能,且消耗更多的资源。
package 设计模式练习;
public class SingletonLazy
{
public static void main(String[] args)
{
President zt1=President.getInstance();
zt1.getName(); //输出总统的名字
President zt2=President.getInstance();
zt2.getName(); //输出总统的名字
if(zt1==zt2)
{
System.out.println("他们是同一人!");
}
else
{
System.out.println("他们不是同一人!");
}
}
}
class President
{
private static volatile President instance=null; //保证instance在所有线程中同步
//private避免类在外部被实例化
private President()
{
System.out.println("产生一个总统!");
}
public static synchronized President getInstance()
{
//在getInstance方法上加同步
if(instance==null)
{
instance=new President();
}
else
{
System.out.println("已经有一个总统,不能产生新总统!");
}
return instance;
}
public void getName()
{
System.out.println("我是美国总统:特朗普。");
}
}
运行效果:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-M8ozOCXb-1589535477961)(en-resource://database/1116:1)]
美国总统只有特朗普一人,所以适用于单例模式。
第一次调用时,判断特朗普没有被构造,就调用构造函数,构造特朗普。
再一次调用,get函数发现,特朗普已经存在了,就不再构造,而只是返回特朗普。
3.饿汉方式实现单例模式
特点就是类一旦加载就创建一个单例,保证在调用 getInstance 方法之前单例已经存在了。
相比于lazy方式更加安全
/*
饿汉式单例
该模式的特点是类一旦加载就创建一个单例,保证在调用 getInstance 方法之前单例已经存在了。
*/
package 设计模式练习;
import java.awt.*;
import javax.swing.*;
public class SingletonEager
{
public static void main(String[] args)
{
JFrame jf=new JFrame("饿汉单例模式测试");
jf.setLayout(new GridLayout(1,2));
Container contentPane=jf.getContentPane();
Bajie obj1=Bajie.getInstance();
contentPane.add(obj1);
Bajie obj2=Bajie.getInstance();
contentPane.add(obj2);
if(obj1==obj2)
{
System.out.println("他们是同一人!");
}
else
{
System.out.println("他们不是同一人!");
}
jf.pack();
jf.setVisible(true);
jf.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
}
}
class Bajie extends JPanel
{
private static Bajie instance=new Bajie();
private Bajie()
{
JLabel l1=new JLabel(new ImageIcon("src/Bajie.jpg"));
this.add(l1);
}
public static Bajie getInstance()
{
return instance;
}
}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-6A2T5cZ5-1589535477961)(en-resource://database/1118:1)]
四、原型模式
1.应用场景
在有些系统中,存在大量相同或相似对象的创建问题,如果用传统的构造函数来创建对象,会比较复杂且耗时耗资源。而复制就比较简单省事了。只需要复制后进行一些小的修改就可以使用。
2.简单的原型模式演示——悟空程序
孙悟空拔下猴毛轻轻一吹就变出很多孙悟空,这实际上是用到了原型模式。这里的孙悟空类 SunWukong 是具体原型类,而 Java 中的 Cloneable 接口是抽象原型类。
/*
* 原型模式
原型模式通常适用于以下场景。
对象之间相同或相似,即只是个别的几个属性不同的时候。
对象的创建过程比较麻烦,但复制比较简单的时候。
*
*/
package 设计模式练习;
import java.awt.*;
import javax.swing.*;
class SunWukong extends JPanel implements Cloneable
{
private static final long serialVersionUID = 5543049531872119328L;
public SunWukong()
{
JLabel l1=new JLabel(new ImageIcon("src/Wukong.jpg"));
this.add(l1);
}
//实现Cloneable接口中的clone方法
public Object clone()
{
SunWukong w=null;
try
{
w=(SunWukong)super.clone();
}
catch(CloneNotSupportedException e)
{
System.out.println("拷贝悟空失败!");
}
return w;
}
}
public class Prototype_SunWukong
{
public static void main(String[] args)
{
JFrame jf=new JFrame("原型模式测试");
jf.setLayout(new GridLayout(1,2));
Container contentPane=jf.getContentPane();
//具体原型类中构造函数为public,所以能直接使用new构造
SunWukong obj1=new SunWukong();
contentPane.add(obj1);
//其他对象的生成可以使用clone
SunWukong obj2=(SunWukong)obj1.clone();
contentPane.add(obj2);
jf.pack();
jf.setVisible(true);
jf.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
}
}
结果演示:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-opvilKjZ-1589535477963)(en-resource://database/1120:1)]
图中出现的两个悟空,一个是正常new方法构造的,而另一个则是使用clone方法构造的。
本例中,图片的构造方法较为简单,看不到两种构造方式明显的效率差别。但对于构造非常复杂的实例,使用clone方法有明显的效率提高。
3.带原型管理器的原型模式
带原型管理器的原型模式与上面最大的区别就是,已经有多个原型,可以直接通过圆形管理器来克隆。
原型模式包含以下主要角色。
- 抽象原型类:规定了具体原型对象必须实现的接口。
- 具体原型类:实现抽象原型类的 clone() 方法,它是可被复制的对象。
- 访问类:使用具体原型类中的 clone() 方法来复制新的对象。
- 原型管理器:该类用 HashMap 保存多个复制的原型,Client 类可以通过管理器的 get(String id) 方法从中获取复制的原型
下面分析一个经典的例子,形状产生器:
用带原型管理器的原型模式来生成包含“圆”和“正方形”等图形的原型,并计算其面积。分析:本实例中由于存在不同的图形类,例如,“圆”和“正方形”,它们计算面积的方法不一样,所以需要用一个原型管理器来管理它们。
结构图如下:[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-xbHxhJFU-1589535477964)(en-resource://database/1122:1)]
代码实现:
/*
* 结构和讲解
*/
package 设计模式练习;
import java.util.*;
interface Shape extends Cloneable
{
public Object clone(); //拷贝
public void countArea(); //计算面积
}
class Circle implements Shape
{
public Object clone()
{
Circle w=null;
try
{
w=(Circle)super.clone();
}
catch(CloneNotSupportedException e)
{
System.out.println("拷贝圆失败!");
}
return w;
}
public void countArea()
{
int r=0;
System.out.print("这是一个圆,请输入圆的半径:");
Scanner input=new Scanner(System.in);
r=input.nextInt();
System.out.println("该圆的面积="+3.1415*r*r+"\n");
}
}
class Square implements Shape
{
public Object clone()
{
Square b=null;
try
{
b=(Square)super.clone();
}
catch(CloneNotSupportedException e)
{
System.out.println("拷贝正方形失败!");
}
return b;
}
public void countArea()
{
int a=0;
System.out.print("这是一个正方形,请输入它的边长:");
Scanner input=new Scanner(System.in);
a=input.nextInt();
System.out.println("该正方形的面积="+a*a+"\n");
}
}
class ProtoTypeManager
{
private HashMap<String, Shape>ht=new HashMap<String,Shape>();
public ProtoTypeManager()
{
ht.put("Circle",new Circle());
ht.put("Square",new Square());
}
public void addshape(String key,Shape obj)
{
ht.put(key,obj);
}
public Shape getShape(String key)
{
Shape temp=ht.get(key);
return (Shape) temp.clone();
}
}
public class ProtoTypeShape
{
public static void main(String[] args)
{
ProtoTypeManager pm=new ProtoTypeManager();
Shape obj1=(Circle)pm.getShape("Circle");
obj1.countArea();
Shape obj2=(Shape)pm.getShape("Square");
obj2.countArea();
}
}
运行结果:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-GlHuU5bt-1589535477965)(en-resource://database/1124:1)]
分析:只需要创建一个原型管理器pm,通过传不同的名称,可以生成不同的形状(这一点有点像简单工厂模式)。但是原型模式与简单工厂模式相比,原型模式是通过clone方式创建的,而不使用new方法,创建效率上更有优势。
五、工厂模式
工厂模式有:简单工厂模式、工厂方法模式、抽象工厂模式三种。
-
简单工厂模式:定义一个工厂类,根据传入的参数不同返回不同的实例,被创建的实例具有共同的父类或接口。
其实由定义也大概能推测出其使用场景,首先由于只有一个工厂类,所以工厂类中创建的对象不能太多,否则工厂类的业务逻辑就太复杂了,其次由于工厂类封装了对象的创建过程,所以客户端应该不关心对象的创建。总结一下适用场景:
(1)需要创建的对象较少。
(2)客户端不关心对象的创建过程。 -
工厂方法模式:
工厂方法模式是简单工厂的仅一步深化, 在工厂方法模式中,我们不再提供一个统一的工厂类来创建所有的对象,而是针对不同的对象提供不同的工厂。也就是说每个对象都有一个与之对应的工厂。定义:
定义一个用于创建对象的接口,让子类决定将哪一个类实例化。工厂方法模式让一个类的实例化延迟到其子类。
适用于:
(1)客户端不需要知道它所创建的对象的类。例子中我们不知道每个类具体叫什么名,只知道创建它的工厂名就完成了创建过程。 (2)客户端可以通过子类来指定创建对应的对象。 -
抽象工厂方法:将抽象工厂引入工厂方法。其不满足开闭原则,也有很多局限,在一些特别的场景使用(比如跨平台)。作为设计模式初学者,战略性放弃,以后再学。
一个工厂方法模式的实例:
用工厂方法模式设计畜牧场。
分析:有很多种类的畜牧场,如养马场用于养马,养牛场用于养牛,所以该实例用工厂方法模式比较适合。
对养马场和养牛场等具体工厂类,只要定义一个生成动物的方法 newAnimal() 即可。由于要显示马类和牛类等具体产品类的图像,所以它们的构造函数中用到了 JPanel、JLabd 和 ImageIcon 等组件,并定义一个 show() 方法来显示它们。
package FactoryMethod;
import java.awt.*;
import javax.swing.*;
public class AnimalFarmTest
{
public static void main(String[] args)
{
try
{
Animal a;
AnimalFarm af;
af=(AnimalFarm) ReadXML2.getObject();
a=af.newAnimal();
a.show();
}
catch(Exception e)
{
System.out.println(e.getMessage());
}
}
}
//抽象产品:动物类
interface Animal
{
public void show();
}
//具体产品:马类
class Horse implements Animal
{
JScrollPane sp;
JFrame jf=new JFrame("工厂方法模式测试");
public Horse()
{
Container contentPane=jf.getContentPane();
JPanel p1=new JPanel();
p1.setLayout(new GridLayout(1,1));
p1.setBorder(BorderFactory.createTitledBorder("动物:马"));
sp=new JScrollPane(p1);
contentPane.add(sp, BorderLayout.CENTER);
JLabel l1=new JLabel(new ImageIcon("src/A_Horse.jpg"));
p1.add(l1);
jf.pack();
jf.setVisible(false);
jf.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); //用户点击窗口关闭
}
public void show()
{
jf.setVisible(true);
}
}
//具体产品:牛类
class Cattle implements Animal
{
JScrollPane sp;
JFrame jf=new JFrame("工厂方法模式测试");
public Cattle()
{
Container contentPane=jf.getContentPane();
JPanel p1=new JPanel();
p1.setLayout(new GridLayout(1,1));
p1.setBorder(BorderFactory.createTitledBorder("动物:牛"));
sp=new JScrollPane(p1);
contentPane.add(sp,BorderLayout.CENTER);
JLabel l1=new JLabel(new ImageIcon("src/A_Cattle.jpg"));
p1.add(l1);
jf.pack();
jf.setVisible(false);
jf.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); //用户点击窗口关闭
}
public void show()
{
jf.setVisible(true);
}
}
//抽象工厂:畜牧场
interface AnimalFarm
{
public Animal newAnimal();
}
//具体工厂:养马场
class HorseFarm implements AnimalFarm
{
public Animal newAnimal()
{
System.out.println("新马出生!");
return new Horse();
}
}
//具体工厂:养牛场
class CattleFarm implements AnimalFarm
{
public Animal newAnimal()
{
System.out.println("新牛出生!");
return new Cattle();
}
}
XML文件内容:提供工厂名
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Qr97fEw0-1589535477967)(en-resource://database/1132:1)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-50wqTTDR-1589535477967)(en-resource://database/1134:1)]
分析:XML文件中只需要给出工厂名,不需要关心具体类名便可以完成创建。
工厂方法适用于这些场景
(1)客户端不需要知道它所创建的对象的类。例子中我们不知道每个类具体叫什么名,只知道创建它的工厂名就完成了创建过程。 (2)客户端可以通过子类来指定创建对应的对象。
六、观察者模式
1作用:对象之间有一对多的依赖关系时。当一个对象的状态发生变化,所有依赖它的对象都发生变化。
应用场景举例1:excel中的图表。表格对象,柱状图对象,折线图对象都可以用来表示数据对象的信息。当你对数据有任何更改的时候,三者会同步发生更改。在这一场景中,数据对象是目标,其余三者是观察者。顾名思义,观察目标的变化。
我遇到的场景:OpenGL编程中会用到的监听函数。比如键盘监听,鼠标监听,只需要继承一个监视器接口,然后重写反应函数。下次鼠标动作时,就会自动执行反应函数中的内容。从开发者的角度讲,这样的设计让我可以不用关心鼠标和键盘内部的实现,只需要记住监听器名字,就可以完成开发,十分方便。QT中的信号机制也有这种思想,但是具体是不是观察者模式就搞不清楚了。
2.观察者模式的主要角色如下。
- 抽象主题(Subject)角色:也叫抽象目标类,它提供了一个用于保存观察者对象的聚集类和增加、删除观察者对象的方法,以及通知所有观察者的抽象方法。
- 具体主题(Concrete Subject)角色:也叫具体目标类,它实现抽象目标中的通知方法,当具体主题的内部状态发生改变时,通知所有注册过的观察者对象。
- 抽象观察者(Observer)角色:它是一个抽象类或接口,它包含了一个更新自己的抽象方法,当接到具体主题的更改通知时被调用。
- 具体观察者(Concrete Observer)角色:实现抽象观察者中定义的抽象方法,以便在得到目标的更改通知时更新自身的状态。
一个简单的观察者模式的实例:
package 设计模式练习;
import java.util.*;
public class ObserverPattern
{
public static void main(String[] args)
{
Subject subject=new ConcreteSubject();
Observer obs1=new ConcreteObserver1();
Observer obs2=new ConcreteObserver2();
subject.add(obs1);
subject.add(obs2);
subject.notifyObserver();
}
}
//抽象目标
abstract class Subject
{
protected List<Observer> observers=new
ArrayList<Observer>();
//增加观察者方法
public void add(Observer observer)
{
observers.add(observer);
}
//删除观察者方法
public void remove(Observer observer)
{
observers.remove(observer);
}
public abstract void notifyObserver(); //通知观察者方法
}
//具体目标
class ConcreteSubject extends Subject
{
public void notifyObserver()
{
System.out.println("具体目标发生改变...");
System.out.println("--------------");
for(Object obs:observers)
{
((Observer)obs).response();
}
}
}
//抽象观察者
interface Observer
{
void response(); //反应
}
//具体观察者1
class ConcreteObserver1 implements Observer
{
public void response()
{
System.out.println("具体观察者1作出反应!");
}
}
//具体观察者1
class ConcreteObserver2 implements Observer
{
public void response()
{
System.out.println("具体观察者2作出反应!");
}
}
运行效果:[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-PWTkbd70-1589535477969)(en-resource://database/1133:1)]