目录
为什么需要设计模式
设计模式是软件设计中常见问题的通用可重用的解决方案,与语言无关。通过引入设计模式,可以更好的提高代码复用性、灵活性、扩展性。
程序设计原则
程序设计也需要遵循很多原则,开闭原则就是说对扩展开放,对修改关闭。里氏代换原则,任何基类可以出现的地方,子类一定可以出现。依赖倒转原则、接口隔离原则、迪米特法则、合成复用原则。
设计模式的分类
创建型模式,共五种:工厂方法模式、抽象工厂模式、单例模式、建造者模式、原型模式。
结构型模式,共七种:适配器模式、装饰器模式、代理模式、外观模式、桥接模式、组合模式、享元模式。
行为型模式,共十一种:策略模式、模板方法模式、观察者模式、迭代子模式、责任链模式、命令模式、备忘录模式、状态模式、访问者模式、中介者模式、解释器模式。
单例模式
- 概念
- 要点
- 优缺点
- 适用场景
- 单例模式要素
- 实例
概念:单例模式是一种软件设计模式,使用单例模式可以确保系统中一个类只有一个实例,即一个类只有一个对象实例(借鉴一下百度百科解释)
要点:
① 使用单例模式的某个类只能有一个实例(单例,单例肯定有且仅有一个实例对象)
② 该类必须自己创建实例对象(因为构造方法私有化,不能new对象,只能自己创建)
③ 必须自行的向整个系统提供创建的这个实例对象(提供一个static方法向外部系统提供这个对象的实例)
优缺点:
优点① 提供唯一实例的访问控制 (实例控制)
② 避免了频繁的创建和销毁对象,提供系统性能,同时只有一个对象,避免了内存空间的浪费,节约系统资源;
③ 类控制实例化过程,所以可以比较灵活的更改实例过程(灵活性)
缺点① 没有抽象层,不易于扩展
② 单例类职责过多,违背"单一职责原则"
③ 滥用单例会造成系统崩溃
适用场景: (对象只需要实例化一次的时候)
单例模式只允许创建一个对象,因此节省内存,加快对象访问速度,因此对象需要被公用的场合适合使用,如多个模块使用同一个数据源连接对象等等。如:
1.需要频繁实例化然后销毁的对象。
2.创建对象时耗时过多或者耗资源过多,但又经常用到的对象。
3.有状态的工具类对象。
4.频繁访问数据库或文件的对象。
以下都是单例模式的经典使用场景:
1.资源共享的情况下,避免由于资源操作时导致的性能或损耗等。如上述中的日志文件,应用配置。
2.控制资源的情况下,方便资源之间的互相通信。如线程池等。
应用场景举例:
1.外部资源:每台计算机有若干个打印机,但只能有一个PrinterSpooler,以避免两个打印作业同时输出到打印机。内部资源:大多数软件都有一个(或多个)属性文件存放系统配置,这样的系统应该有一个对象管理这些属性文件
2. Windows的Task Manager(任务管理器)就是很典型的单例模式(这个很熟悉吧),想想看,是不是呢,你能打开两个windows task manager吗? 不信你自己试试看哦~
3. windows的Recycle Bin(回收站)也是典型的单例应用。在整个系统运行过程中,回收站一直维护着仅有的一个实例。
4. 网站的计数器,一般也是采用单例模式实现,否则难以同步。
5. 应用程序的日志应用,一般都何用单例模式实现,这一般是由于共享的日志文件一直处于打开状态,因为只能有一个实例去操作,否则内容不好追加。
6. Web应用的配置对象的读取,一般也应用单例模式,这个是由于配置文件是共享的资源。
7. 数据库连接池的设计一般也是采用单例模式,因为数据库连接是一种数据库资源。数据库软件系统中使用数据库连接池,主要是节省打开或者关闭数据库连接所引起的效率损耗,这种效率上的损耗还是非常昂贵的,因为何用单例模式来维护,就可以大大降低这种损耗。
8. 多线程的线程池的设计一般也是采用单例模式,这是由于线程池要方便对池中的线程进行控制。
9. 操作系统的文件系统,也是大的单例模式实现的具体例子,一个操作系统只能有一个文件系统。
单例模式要素:
a.私有构造方法
b.私有静态引用指向自己实例
c.以自己实例为返回值的公有静态方法
实例1:
/**
* 饿汉单例模式
*/
public class HungrySingleton {
//定义私有的静态实例对象,只能通过当前类提供的方法获取
private static final HungrySingleton instance = new HungrySingleton();
//私有的构造方法,禁止被实例化和继承
private HungrySingleton() {
}
//提供一个静态方法来获取当前实例对象
public static HungrySingleton getInstance() {
return instance;
}
}
优点:解决了多线程访问设计到的内存可见性,达到线程安全的目的;
缺点:涉及到加载问题,如果系统中存在大量懒汉单例模式,那么可能很多类在被实例化之前就已经初始化了,从而造成资源浪费,降低系统性能。
实例2:
/**
* 懒汉模式又叫延迟模式
*/
public class LazySingleton {
//定义类静态属性
private static LazySingleton instance;
//私有化构造方法
private LazySingleton() {
}
//向外部提供一个获取单例的接口
public LazySingleton getInstance() {
if (instance == null) {
return new LazySingleton();
}
return instance;
}
}
优点:保证了对象的单一实例,节省内存空间
缺点:存在线程安全问题,线程不安全
实例3 (V-C-L模式)
/**
* 考虑多线程并发解决的有一种安全单例模式
* 设计技术:volatile关键字
* synchronized同步锁
* 双重检查
* 建议:尽量在适用synchronized时候,避免直接锁整个方法,如果这样做的话,将导致额外的性能开销;
*/
public class SafeSingleton {
//1、定义一个静态属性
private volatile static SafeSingleton instance;
//2、构造方法私有化
private SafeSingleton() {
}
//对外部开放唯一实例接口
public SafeSingleton getInstance() {
if (instance == null) { // 第1次检查
synchronized (SafeSingleton.class) { //加锁
if (instance == null) { // 第2次检查
return new SafeSingleton();
}
} //释放锁
}
return instance;
}
}
优点:既实现了单一实例原则也保障了线程安全问题,同时也尽量的避免了不必要的性能开销,加上volatile关键字避免了指令重排序造成的不良影响。
实例4:holder模式
public class HolderDemo {
//保证:唯一性,线程安全;性能好;懒加载
public HolderDemo() {
}
private static class Holder {
private static HolderDemo instanse = new HolderDemo();
}
private static HolderDemo getInstance() {
return Holder.instanse;
}
}
实例5:枚举模式
public class SingletonDemo {
private enum EnumHolder {
//可以理解为常量,类加载的时候被执行,且被执行一次,所属类型为所在类【SingletonDemo】
INSTANSE;
private SingletonDemo instance;
EnumHolder() {
this.instance = new SingletonDemo();
}
private SingletonDemo getInstance() {
return instance;
}
}
private static SingletonDemo getInstance() {
return EnumHolder.INSTANSE.instance;
}
}
工厂模式
- 简单工厂模式(静态工厂)
- 工厂模式
- 抽象工厂模式
简单工厂模式
目的:定义创建类的接口。
组成部分:
1)工厂角色 ---> 主要是用于生产实例对象工厂类
2) 抽象产品 ---> 一般是一个接口或者抽象类,用于被具体产品的实现或者继承
3) 具体产品 ---> 具体的产品,相对于我们自己new的具体实例对象
代码展示:
工厂角色部分代码
/** * 这是电脑的抽象工厂 */ public class ComputerFactory { public Computer production(Class clazz) { Computer computer = null; try { //此处通过类加载器来实现简单的类型获取 computer = (Computer) Class.forName(clazz.getName()).newInstance(); } catch (InstantiationException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); } catch (ClassNotFoundException e) { e.printStackTrace(); } return computer; } }
抽象产品类代码展示
/** * 一个简单的电脑产品抽象类 */ public abstract class Computer { public abstract void produceName(); }
具体产品代码展示
public class DellComputer extends Computer{ public void produceName() { System.out.println("生产戴尔笔记本电脑......"); } }
public class ThinkPadComputer extends Computer { public void produceName() { System.out.println("生产联想笔记本电脑......"); } }
测试类
public class SimpleTest { public static void main(String[] args) { // 获取电脑工厂 ComputerFactory computerFactory = new ComputerFactory(); // 生产戴尔笔记本电脑对象 Computer dellComputer = computerFactory.production(DellComputer.class); dellComputer.produceName(); // 获得联系笔记本电脑对象 Computer thinkPadComputer = computerFactory.production(ThinkPadComputer.class); thinkPadComputer.produceName(); } }
优点:工程类有必要的判断,可以实现对象的实例;
确定:只能适用于少量的对象实例,并且额不易于扩展,违背了开闭原则;
PS:何为开闭原则,是指面向对象设计的六大基本原则之一,即:一个软件实体应当遵循对外开放,对内代码修改关闭;就是说软件实体尽量在不许改源代码的前提下实现在程序的扩展;
工厂模式
实质:定义创建类的接口,具体的实例交给其子类去觉得实例化那个类,使类的实例化得到了延迟.
组成部分:
1) 抽象工厂角色 ---> 工厂的核心,定义做什么的接口或者抽象类,具体怎么做,就是其子类实现工厂决定
2) 具体工厂角色 ---> 实现抽象工厂,定义怎么做
3) 抽象产品角色 ---> 具体实体产品的接口或抽象类
4) 具体产品角色 ---> 实现抽象角色
代码展示:
抽象工厂部分
/** * 这是电脑的抽象工厂 */ public interface ComputerFactory { Computer production(); }
具体工厂
/** * 戴尔厂商实现抽象工厂接口去生产戴尔笔记本电脑 */ public class DellFactory implements ComputerFactory { public Computer production() { return new DellComputer(); } }
/** * 联想厂商实现抽象工厂接口用于生产联想笔记本电脑 */ public class ThinkPadFactory implements ComputerFactory { public Computer production() { return new ThinkPadComputer(); } }
抽象产品接口/具体产品接口 (同上简单工厂)
测试代码类
public static void main(String[] args) { // 获取戴尔厂商 ComputerFactory dellFactory = new DellFactory(); // 生产戴尔笔记本电脑 Computer dellComputer = dellFactory.production(); dellComputer.produceName(); // 获得联系厂商 ComputerFactory thinkPadFactory = new ThinkPadFactory(); // 生产联想笔记本电脑 Computer thinkPadComputer = thinkPadFactory.production(); thinkPadComputer.produceName(); }
优点:向客户隐藏了对象实例的这一细节,具体的实例对象由工厂来创建,客户只需要
创建对应的工厂即可,即实现了系统良好的可扩展性也满足"开闭原则";
缺定:当我们需要新的产品的时候,那么就需要重新新建产品工厂并且实现抽象接口,如果
系统中需要非常多的不同类别的产品,那么我们就需要新建很多工厂,这么的话无形间增加
工作量并且也增大了维修成本,同时更多的类需要加载和编译,为系统带来了额外的开销。
抽象工厂模式
概念:
向客户端提供一个接口,在客户端不必指明特定产品类型的前提下能够创建多个产品族的产品对象.
PS:产品族是 同类型的产品对象的集合。(比如商务车和跑车就属于不同产品族群,)
商务车族群可以有奔驰、大众;跑车族群也可以有大众,法拉第;
组成部分:
1) 系统中有多个产品族,而系统一次只可能消费其中一族产品。
2) 同属于同一个产品族的产品以其使用。
来看看抽象工厂模式的各个角色(和工厂方法的如出一辙):
1) 抽象工厂角色: 这是工厂方法模式的核心,它与应用程序无关。是具体工厂角色必须实现的接口或者必须继承的父类。在java中它由抽象类或者接口来实现。
2) 具体工厂角色:它含有和具体业务逻辑有关的代码。由应用程序调用以创建对应的具体产品的对象。在java中它由具体的类来实现。
3) 抽象产品角色:它是具体产品继承的父类或者是实现的接口。在java中一般有抽象类或者接口来实现。
4) 具体产品角色:具体工厂角色所创建的对象就是此角色的实例。在java中由具体的类来实现代码展示:
抽象工厂
/** * 一个抽象工厂接口,主要目的是定义生产各个类型的电脑 * 这个的每个类型就相当于我们抽象工厂模式所说的产品族 * 这里一个三个产品族. */ public interface AbstractFactory { //生产军用电脑 MilitaryComputer getMilitary(); //生产民用电脑 CivilComputer getCivil(); //商用电脑 BusinessComputer getBusiness(); }
具体工厂
/** * 联想品牌的电脑工厂,实现生产各类型的联想牌子笔记本电脑 */ public class ThinkPadComputerFactory implements AbstractFactory { public MilitaryComputer getMilitary() { return new ThinkPadMilitaryComputer(); } public CivilComputer getCivil() { return new ThinkPadCivilComputer(); } public BusinessComputer getBusiness() { return new ThinkPadBusinessComputer(); } }
/**
* 戴尔品牌的电脑工厂,实现生产各类型的戴尔牌子笔记本电脑
*/
public class DellComputerFactory implements AbstractFactory{
public MilitaryComputer getMilitary() {
return new DellMilitaryComputer();
}
public CivilComputer getCivil() {
return new DellCivilComputer();
}
public BusinessComputer getBusiness() {
return new DellBusinessComputer();
}
}
抽象产品
/** * 定义商用电脑抽象类 */ public abstract class BusinessComputer { public abstract void produceName(); }
/**
* 定义民用电脑抽象类
*/
public abstract class CivilComputer{
public abstract void produceName();
}
/** * 定义军用电脑抽象类 */ public abstract class MilitaryComputer { public abstract void produceName(); }
具体产品
public class DellBusinessComputer extends BusinessComputer {
public void produceName() {
System.out.println("戴尔商用笔记本电脑");
}
}
public class DellCivilComputer extends CivilComputer { public void produceName() { System.out.println("戴尔民用笔记本电脑"); } }
public class DellMilitaryComputer extends MilitaryComputer {
public void produceName() {
System.out.println("戴尔军用笔记本电脑");
}
}
public class ThinkPadBusinessComputer extends BusinessComputer {
public void produceName() {
System.out.println("联想商用笔记本电脑");
}
}
public class ThinkPadCivilComputer extends CivilComputer {
public void produceName() {
System.out.println("联想民用笔记本电脑");
}
}
public class ThinkPadMilitaryComputer extends MilitaryComputer {
public void produceName() {
System.out.println("联想军用笔记本电脑");
}
}
测试类
public class AbstractTest {
public static void main(String[] args) {
// 获得戴尔工厂
AbstractFactory dellComputerFactory = new DellComputerFactory();
// 获得戴尔民用笔记本电脑
CivilComputer dellCivilComputer = dellComputerFactory.getCivil();
//获得戴尔军用笔记本电脑
MilitaryComputer dellMilitaryComputer = dellComputerFactory.getMilitary();
//获得戴尔商用笔记本电脑
BusinessComputer dellBusinessComputer = dellComputerFactory.getBusiness();
dellCivilComputer.produceName();
dellMilitaryComputer.produceName();
dellBusinessComputer.produceName();
System.out.println(" ");
//获取联想工厂
AbstractFactory thinkPadComputerFactory = new ThinkPadComputerFactory();
//生产联想民用笔记本电脑实例
CivilComputer thinkPadCivilComputer = thinkPadComputerFactory.getCivil();
//生产联想军用笔记本电脑
MilitaryComputer thinkPadMilitaryComputer = thinkPadComputerFactory.getMilitary();
//生产联想商用笔记本电脑
BusinessComputer thinkPadBusinessComputer = thinkPadComputerFactory.getBusiness();
thinkPadCivilComputer.produceName();
thinkPadMilitaryComputer.produceName();
thinkPadBusinessComputer.produceName();
}
控制台输出结果 :戴尔民用笔记本电脑
戴尔军用笔记本电脑
戴尔商用笔记本电脑
联想民用笔记本电脑
联想军用笔记本电脑
联想商用笔记本电脑
优缺点
优点:
1)封装性。每个产品的实现类不是高层模块要关心的,它要关心的是接口,是抽象,它不关心对象是如何创建出来的,这都由工厂类负责的,只要知道工厂类是谁,我就能创建一个需要的对象,省时省力。
2)产品族内的约束为非公开状态。例如生产男女比例的问题上,猜想女娲娘娘肯定有自己的打算,那么在抽象工厂模式中,这些约束都在工厂内里面实现的。
缺点:
1) 产品族扩展比较困难,也就说我们如果在增加一个"科研用的电脑"产品族类,那么我们需要修改
AbstractFactory 以及它的实现类
ThinkPadComputerFactory
DellComputerFactory
同时还需要新增: ScientificComputer 、ThinkPadScientificComputer 、 DellScientificComputer
2) 如果我们仅仅是增加一个产品品牌,比如说,增加一个appleFactory来生产各种类型的笔记本电脑,那么就非常简单,只需要新增几个简单的类
AppleComputerFactory 、AppleMilitaryComputer 、AppleBussionComputer 、 AppleCivilComputer
AppleBusinessComputer
而不用修改其他代码;;;特简单方便,同时也实现代码的降耦合。
总结来说就是:抽象工厂模式横向扩展容易,纵向扩展比较麻烦.
横坐标:相当于我们的不同品牌厂商(dell、thinkPad、apple)
纵坐标:相当于我们的电脑的不同用途(busines、civil、military)
每个纵坐标就是一个具体工厂,横坐标的个数就是工厂生产的产品实例