一、什么是设计模式?
设计模式(Design pattern)是一套被反复使用、多数人知晓的、经过分类编目的、代码设计经验的总结。使用设计模式是为了可重用代码、让代码更容易被他人理解、保证代码可靠性。 毫无疑问,设计模式于己于他人于系统都是多赢的,设计模式使代码编制真正工程化,设计模式是软件工程的基石,如同大厦的一块块砖石一样。项目中合理的运用设计模式可以完美的解决很多问题,每种模式在现在中都有相应的原理来与之对应,每一个模式描述了一个在我们周围不断重复发生的问题,以及该问题的核心解决方案,这也是它能被广泛应用的原因。
二、设计模式分类
总体来说设计模式分为三大类:
创建型模式(5种):工厂方法模式、抽象工厂模式、单例模式、建造者模式、原型模式。
结构型模式(7种):适配器模式、装饰器模式、代理模式、外观模式、桥接模式、组合模式、享元模式。
行为型模式(11种):策略模式、模板方法模式、观察者模式、迭代子模式、责任链模式、命令模式、备忘录模式、状态模式、访问者模式、中介者模式、解释器模式。
三、创建型模式之单例模式
单例(Singleton)模式:某个类只能生成一个实例,该类提供了一个全局访问点供外部获取该实例,其拓展是有限多例模式。
这样的模式有几个好处:
- 某些类创建比较频繁,对于一些大型的对象,这是一笔很大的系统开销。
- 省去了new操作符,降低了系统内存的使用频率,减轻GC压力。
- 有些类如交易所的核心交易引擎,控制着交易流程,如果该类可以创建多个的话,系统完全乱了。(比如一个军队出现了多个司令员同时指挥,肯定会乱成一团),所以只有使用单例模式,才能保证核心交易服务器独立控制整个流程。
优点:
只有一个实例,节约了内存资源,提高了系统性能
缺点:
没有抽象层,不能扩展
职责过重,违背了单一性原则
举例:
//(1)饿汉式单例模式,是立即加载的方式,无论是否会用到这个对象,都会加载。
//如果在构造方法里写了性能消耗较大,占时较久的代码,比如建立与数据库的连接,那么就会在启动的时候
//感觉稍微有些卡顿。
public class GiantDragon {
//私有化构造方法使得该类无法在外部通过new 进行实例化
private GiantDragon(){
}
//准备一个类属性,指向一个实例化对象。 因为是类属性,所以只有一个
private static GiantDragon instance = new GiantDragon();
//public static 方法,提供给调用者获取12行定义的对象
public static GiantDragon getInstance(){
return instance;
}
}
//(2)饿汉式单例模式,是延迟加载的方式,只有使用的时候才会加载。
public class GiantDragon {
//私有化构造方法使得该类无法在外部通过new 进行实例化
private GiantDragon(){
}
//准备一个类属性,用于指向一个实例化对象,但是暂时指向null
private static GiantDragon instance;
//public static 方法,返回实例对象
public static GiantDragon getInstance(){
//第一次访问的时候,发现instance没有指向任何对象,这时实例化一个对象
if(null==instance){
instance = new GiantDragon();
}
//返回 instance指向的对象
return instance;
}
}
//使用懒汉式,在启动的时候,会感觉到比饿汉式略快,因为并没有做对象的实例化。
//但是在第一次调用的时候,会进行实例化操作,感觉上就略慢。
优化上述单例模式:上述的单例模式在多线程下就会出现同时调用这个单例,无线程安全保护就会出现问题,所以需要优化上述的单例模式,考虑用到synchronized关键字锁住这个对象,单例模式使用内部类来维护单例的实现,JVM内部的机制能够保证当一个类被加载的时候,这个类的加载过程是线程互斥的。这样当我们第一次调用getInstance的时候,JVM能够帮我们保证instance只被创建一次,并且会保证把赋值给instance的内存初始化完毕。
public class GiantDragon {
private static GiantDragon instance = null;
private Vector properties = null;
public Vector getProperties() {
return properties;
}
private GiantDragon() {
}
//getInstance()与创建实例分开是为了避免构造函数抛出异常时,实例将不会创建
private static synchronized void syncInit() {
if (instance == null) {
instance = new GiantDragon();
}
}
public static GiantDragon getInstance() {
if (instance == null) {
syncInit();
}
return instance;
}
public void updateProperties() {
GiantDragon shadow = new GiantDragon();
properties = shadow.getProperties();
}
}
多例模式:在限定类的对象时候如星期、性别、月份有多少天等,这样的类就不应该由用户无限制地去创造实例化对象,应该只使用有限的几个,这个就属于多例设计模式。不管是单例设计模式还是多例设计模式,有一个核心不可动摇,即构造器方法私有化。
class Sex{
private String title;
private static final Sex MALE = new Sex("男");
private static final Sex FEMALE = new Sex("女");
private Sex(String title){ //构造器私有化
this.title = title;
}
public String toString(){
return this.title;
}
public static Sex getInstance(int ch){
switch(ch){
case 1:
return MALE;
case 2:
return FEMALE;
default:
return null;
}
}
}
public class TestDemo{
public static void main(String args[]){
Sex sex = Sex.getInstance(2);
System.out.println(sex);
}
}
//==========程序执行结果=========
//女
四、创建型模式之工厂模式
先简单的看一下图片来快速了解简单工厂、工厂模式与抽象工厂模式之间的差别
(1)简单工厂模式
如上图所示,就是建立一个工厂类,对实现了同一接口的一些类进行实例的创建。
简单工厂模式(Simple Factory ):由一个工厂对象参数决定创建出哪一种产品类的实例
违背了开放-封闭原则(简单工厂模式违背了该原则,而工厂模式没有)
简单工厂模式:最大优点在于工厂类中包含了必要的逻辑判断,根据客户端的选择条件动态实例化相关的类,对于客户端来说,去除了于具体产品的依赖
//定义一个生成手机的接口,有一个iphone()需要实现
public interface Iphoneproduct{
public void Iphone();
}
//实现上述接口,分别用两家工厂华为、小米
public class Huawei implements Iphoneproduct{
public void Iphone(){
System.out.println("huawei iphone");
}
}
public class Xiaomi implements Iphoneproduct{
public void Iphone(){
System.out.println("xiaomi iphone");
}
}
//创建一个工厂类
public class Iphoneproductfactory {
public Iphoneproduct product(String type) {
if ("huawei".equals(type)) {
return new Huawei();
} else if ("xiaomi".equals(type)) {
return new Xiaomi();
} else {
System.out.println("请输入正确的类型!");
return null;
}
}
}
//测试验证
public class FactoryTest {
public static void main(String[] args) {
Iphoneproductfactory iphoneproductfactory = new Iphoneproductfactory();
Iphoneproduct iphoneproduct = iphoneproductfactory.product("huawei");
iphoneproduct.Iphone();
}
}
(2)工厂方法模式
工厂方法模式(Factory Method):定义一个用于创建对象的接口,让子类决定实例化哪个类,使一个类的实例化延迟到其子类
缺点:
客户端需要决定实例化一个工厂来实现运算类,选择判断的问题还是存在的,也就是说,工厂方法把简单工厂的内部逻辑判断都移到了客户端代码来进行,你想要加功能,本来是改工厂类,而现在是修改客户端
优点:
1.对象的创建和使用解耦
2.如果创建比较复杂,创建过程统一由工厂管理,减少了重复的代码,方便日后维护
3.当业务扩展时,只需要添加工厂子类,符合开闭原则
这个例子和上面一样,但是需要添加AbstractFactory接口,不同的产品工厂实现这个接口。
public interface AbstractFactory {
public Iphoneproduct iphoneproduct();
}
public class HuaweiFactory implements AbstractFactory {
@Override
public Iphoneproduct iphoneproduct() {
return new Huawei();
}
}
public class XiaomiFactory implements AbstractFactory {
@Override
public Iphoneproduct iphoneproduct() {
return new Xiaomi();
}
}
//这里下面三个和上面简单工厂一致不变
public interface Iphoneproduct{
public void Iphone();
}
public class Huawei implements Iphoneproduct{
public void Iphone(){
System.out.println("huawei iphone");
}
}
public class Xiaomi implements Iphoneproduct{
public void Iphone(){
System.out.println("xiaomi iphone");
}
}
//测试验证
public class FactoryTest {
public static void main(String[] args) {
HuaweiFactory huaweifactory = new HuaweiFactory();
huaweifactory.iphoneproduct().Iphone();
XiaomiFactory xiaomifactory = new XiaomiFactory();
xiaomifactory.iphoneproduct().Iphone();
}
}
(3)抽象工厂模式
上面两种模式不管工厂怎么拆分抽象,都只是针对一类产品牛奶iphone,如果要生成另一种产品pc,应该怎么表示呢?最简单的方式是把2中介绍的工厂方法模式完全复制一份,不过这次生产的是pc。也就是说我们要完全复制和修改iphone生产管理的所有代码,这样并不利于扩展和维护。抽象工厂模式通过在AbstarctFactory
中增加创建产品的接口,并在具体子工厂中实现新加产品的创建。
最简单的方式直接在上面的工厂模式基础上添加Pc即可,添加和修改的具体代码如下:
//工厂新增pc生产
public interface AbstractFactory {
public Iphoneproduct iphoneproduct();
public Pcproduct pcproduct();
}
//生产pc接口
public interface Pcproduct {
public void Pc();
}
//小米与华为工厂新建生产pc的方法。实现AbstractFactory
public class Huaweifactory implements AbstractFactory {
@Override
public Iphoneproduct iphoneproduct() {
return new Huawei();
}
@Override
public Pcproduct pcproduct(){
return new Xiaomi();
}
}
public class Xiaomifactory implements AbstractFactory {
@Override
public Iphoneproduct iphoneproduct() {
return new Xiaomi();
}
@Override
public Pcproduct pcproduct(){
return new Xiaomi();
}
}
//华为与小米的具体生产业务
public class Huawei implements Iphoneproduct,Pcproduct{
public void Iphone(){
System.out.println("huawei iphone");
}
public void Pc(){
System.out.println("huawei pc");
}
}
public class Xiaomi implements Iphoneproduct,Pcproduct{
public void Iphone(){
System.out.println("xiaomi iphone");
}
public void Pc() {
System.out.println("xiaomi pc");
}
}
//测试
public class FactoryTest {
public static void main(String[] args) {
Huaweifactory huaweifactory = new Huaweifactory();
huaweifactory.iphoneproduct().Iphone();
huaweifactory.pcproduct().Pc();
Xiaomifactory xiaomifactory = new Xiaomifactory();
xiaomifactory.iphoneproduct().Iphone();
xiaomifactory.pcproduct().Pc();
}
}
也不必像上面一样修改原有类(毕竟影响开闭性了),如果有新的类进来,只需要添加一个对应的具体工厂类,不影响现有代码,增加了程序的扩展性
五、创建型模式之建造者模式
使用场景:
1.相同的方法,不同的执行顺序,产生不同的事件结果时,可以采用建造者模式。
2.多个部件或零件,都可以装配到一个对象中,但是产生的运行结果又不相同时,则可以使用该模式。
3.产品类非常复杂,或者产品类中的调用顺序不同产生了不同的效能,这个时候使用建造者模式非常合适。
建造者模式的优点:
1、封装性好,创建和使用分离;
2、扩展性好,建造类之间独立、一定程度上解耦。
建造者模式的缺点:
1、产生多余的Builder对象;
2、产品内部发生变化,建造者都要修改,成本较大。
以课程为例,一个完整的课程需要由PPT课件、回放视频、课堂笔记、课后作业组成,但是这些内容的设置顺序可以随意搭配调整,我们用建造者模式来带入了解一下。首先我们需要创建一个需要构造的产品类Course:
public class Course {
private String name;
private String ppt;
private String video;
private String note;
private String homework;
public void setPpt(String ppt){
this.ppt = ppt;
}
public void setName(String name){
this.name = name;
}
public void setVideo(String video){
this.video = video;
}
public void setNote(String note){
this.note = note;
}
public void setHomework(String homework){
this.homework = homework;
}
@Override
public String toString() {
return "CourseBuilder{" +
"name='" + name + '\'' +
", ppt='" + ppt + '\'' +
", video='" + video + '\'' +
", note='" + note + '\'' +
", homework='" + homework + '\'' +
'}';
}
}
常规模式:创建者建造类CourseBuilder,将复杂的构造过程封装起来,构造步骤由用户决定
public class CourseBuilder{
private Course course = new Course();
public void addName(String name) {
course.setName(name);
}
public void addPPT(String ppt) {
course.setPpt(ppt);
}
public void addVideo(String video) {
course.setVideo(video);
}
public void addNote(String note) {
course.setNote(note);
}
public void addHomework(String homework) {
course.setHomework(homework);
}
public Course build() {
return course;
}
}
在平时的应用中,建造者模式通常是采用链式编程的方式构造对象,下面我们来看一下掩饰代码,修改CourseBuilder类,将Course变为CourseBuilder的内部类,将构造步骤添加进去,每完成一个步骤,都返回this:
public class CourseBuilder {
private Course course = new Course();
public CourseBuilder addName(String name) {
course.setName(name);
return this;
}
public CourseBuilder addPPT(String ppt) {
course.setPpt(ppt);
return this;
}
public CourseBuilder addVideo(String video) {
course.setVideo(video);
return this;
}
public CourseBuilder addNote(String note) {
course.setNote(note);
return this;
}
public CourseBuilder addHomework(String homework) {
course.setHomework(homework);
return this;
}
public Course build() {
return this.course;
}
}
客户端测试:
public class Test {
public static void main(String[] args) {
CourseBuilder builder = new CourseBuilder()
.addName("设计模式")
.addPPT("【PPT课件】")
.addVideo("【回放视频】")
.addNote("【课堂笔记】")
.addHomework("【课后作业】");
System.out.println(builder.build());
}
}
建造者模式和工厂模式的区别
了解建造者模式,那么它和工厂模式有什么区别呢?
1、建造者模式更加注重方法的调用顺序,工厂模式注重于创建对象。
2、创建对象的力度不同,建造者模式创建复杂的对象,由各种复杂的部件组成,工厂模式创建出来的都一样。
3、关注点不一样,工厂模式只需要把对象创建出来就行了,而建造者模式不仅要创建出这个对象,还要知道这个对象由哪些部分组成。
4、建造者模式根据建造过程中的顺序不一样,最终的对象部件组成也不一样。
六、创建型模式之原型模式
原型(Prototype)模式的定义如下:用一个已经创建的实例作为原型,通过复制该原型对象来创建一个和原型相同或相似的新对象。在这里,原型实例指定了要创建的对象的种类。用这种方式创建对象非常高效,根本无须知道对象创建的细节。
一个原型类,只需要实现Cloneable接口,覆写clone方法,此处clone方法可以改成任意的名称,因为Cloneable接口是个空接口,你可以任意定义实现类的方法名,如cloneA或者cloneB,因为此处的重点是super.clone()这句话,super.clone()调用的是Object的clone()方法
使用原型模式的优点:
1.性能优良
原型模式是在内存二进制流的拷贝,要比直接new一个对象性能好很多,特别是要在一个循环体内产生大量的对象时,原型模式可以更好地体现其优点。
2.逃避构造函数的约束
这既是它的优点也是缺点,直接在内存中拷贝,构造函数是不会执行的
使用场景:
1.对象之间相同或相似,即只是个别的几个属性不同的时候,对象的创建过程比较麻烦,但复制比较简单的时候。
2.资源优化场景 类初始化需要消化非常多的资源,这个资源包括数据、硬件资源等。
3.性能和安全要求的场景 通过new产生一个对象需要非常繁琐的数据准备或访问权限,则可以使用原型模式。
4.一个对象多个修改者的场景 一个对象需要提供给其他对象访问,而且各个调用者可能都需要修改其值时,可 以考虑使用原型模式拷贝多个对象供调用者使用。
通用的克隆实现方法:是在具体原型类的克隆方法中实例化一个与自身类型相同的对象并将其返回,并将相关的参数传入新创建的对象中,保证它们的成员属性相同。
class ConcretePrototype implements Prototype
{
private String attr; //成员属性
public void setAttr(String attr)
{
this.attr = attr;
}
public String getAttr()
{
return this.attr;
}
public Prototype clone() //克隆方法
{
Prototype prototype = new ConcretePrototype(); //创建新对象
prototype.setAttr(this.attr);
return prototype;
}
}
浅拷贝和深拷贝:
浅拷贝:Object类提供的方法clone只是拷贝本对象,其对象内部的数组、引用对象等都不拷贝,还是指向原生对象的内部元素地址,这种拷贝就叫做浅拷贝, 其他的原始类型比如int、long、char、string(当做是原始类型)等都会被拷贝。
深拷贝:将一个对象复制后,不论是基本数据类型还有引用类型,都是重新创建的。简单来说,就是深复制进行了完全彻底的复制,而浅复制不彻底。
//深浅拷贝例子
public class Prototype implements Cloneable, Serializable {
private static final long serialVersionUID = 1L;
private String string;
private SerializableObject obj;
/* 浅复制,Object类提供一个clone()方法,可以将一个Java对象复制一份。因此在Java中可以直接使用Object提供的clone()方法来实现对象的克隆*/
public Object clone() throws CloneNotSupportedException {
Prototype proto = (Prototype) super.clone();
return proto;
}
/* 深复制,在Java语言中,如果需要实现深克隆,可以通过序列化(Serialization)等方式来实现。
序列化就是将对象写到流的过程,写到流中的对象是原有对象的一个拷贝,而原对象仍然存在于内存中。
通过序列化实现的拷贝不仅可以复制对象本身,而且可以复制其引用的成员对象,因此通过序列化将对象写到一个流中,再从流里将其读出来,可以实现深克隆。
需要注意的是能够实现序列化的对象其类必须实现Serializable接口,否则无法实现序列化操作。
下面我们使用深克隆技术来实现工作周报和附件对象的复制,由于要将附件对象和工作周报对象都写入流中,因此两个类均需要实现Serializable接口*/
public Object deepClone() throws IOException, ClassNotFoundException {
/* 写入当前对象的二进制流 */
ByteArrayOutputStream bos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(bos);
oos.writeObject(this);
/* 读出二进制流产生的新对象 */
ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
ObjectInputStream ois = new ObjectInputStream(bis);
return ois.readObject();
}
public String getString() {
return string;
}
public void setString(String string) {
this.string = string;
}
public SerializableObject getObj() {
return obj;
}
public void setObj(SerializableObject obj) {
this.obj = obj;
}
}
class SerializableObject implements Serializable {
private static final long serialVersionUID = 1L;
}
注意:使用原型模式时,引用的成员变量必须满足两个条件才不会被拷贝:一 是类的成员变量,而不是方法内变量;二是必须是一个可变的引用对象,而不是一个原始类型或不可变对象。