设计模式的原则
- 开闭原则:程序进行拓展时,不能去修改源码,而是通过扩展原有的代码实现新的功能
- 单一职责原则:每个类实现单一的功能
- 里氏替换原则:子类对父类的方法尽量不要重写或重载,因为父类定义好了结构,通过这个规范的接口与外界交互,子类不应该去破坏它
- 依赖倒转原则:面向接口编程,依赖于抽象而不是具体实现
- 接口隔离原则:每个接口不存在子类用不到却必须实现的方法,否则就要将接口拆分,使用多个隔离的接口,比使用一个接口(多个接口方法集合到一个接口上)要好
- 迪米特法则:一个类对自己依赖的类知道的越少越好。无论被依赖的类多么复杂,都应该将逻辑封装在方法内部,通过public方法提供给外界。这样当被依赖的类变化时,才能最小的影响该类。
- 合成复用原则:尽量使用合成/聚合的方式,而不是继承
创建型模式,共以下五种:
工厂方法模式
简单工厂模式
//接口 :发射器 public interface Sender{ public void send(); } //实现类 public class MailSender implements Sender{ @Override public void send(){ System.out.println("邮箱发送"); } } public class SmsSender implements Sender{ @Override public void send(){ System.out.println("短信发送"); } }
//工厂类:返回一个发送器对象 如果多个方法改进的话:则分别为每个接口的实现类创造每个方法,以便创造对象。也可以改为静态方法,直接用类名调用。 public class SendFactory{ public Sender produce(String type){ if("mail".equals(type)){ return new MailSender(); }else if("sms".equals(type)){ return new SmsSender(); }else{ System.out.println("请输入正确的类型"); return null; } } }
//测试类 public class FactoryTest{ public static void main(String[] args){ //主要是针对客户端调用者来说简单了许多,不需要关联具体的实现 SendFactory factory = new SendFactory(); Sender sender = factory.prodece("sms"); sender.send(); } }总体来说,工厂模式适合:凡是出现了大量的产品需要创建,并且具有共同的接口时,可以通
过工厂方法模式进行创建。
简单工厂模式的一个问题:类对象的创建需要依赖工厂,如果要拓展程序,必须对工厂类进行修改,这违背了闭包原则。
所以改进方案:工厂方法模式
给工厂也加一个共有接口,为每个业务类都创建一个工厂。
public interface Sender{ public void send(); } //两个业务实现类 public class MailSender implements Sender{ @Override public void send(){ System.out.println("邮件发送"); } } public class SmsSender implements Sender{ @Override public void send(){ System.out.println("短信发送"); } }
public interface Provider{ public Sender produce(); } //两个业务类,对应两个工厂类,分别创建自己的对象 public class SendMailFactory implements Provider{ @Override public Sender produce(){ return new MailSender(); } } public class SendSmsFactory implements Provider{ @Override public Sender produce(){ return new SmsSender(); } }
//测试类 public class Test{ public static void main(String[] args){ Provoider provider = new SenderMailFactory(); //创建一个邮件工厂 Sender sender = provider.produce(); sender.send(); } }其实这个模式的好处就是,如果你现在想增加一个功能:发及时信息,则只需做一个实现类,实现 Sender 接口,同时做一个工厂类,实现 Provider 接口,就 OK 了,无需去改动现成的代码。
弊端就是在系统功能扩展的时候,会额外增加很多对应的类。
抽象工厂模式
对比上面的工厂模式区别:
工厂模式:
- 一个抽象产品类,可以派生出多个具体产品类。
- 一个抽象工厂类,可以派生出多个具体工厂类。
- 每个具体工厂类只能创建一个具体产品类的实例。
抽象工厂模式:
- 多个抽象产品类,每个抽象产品类可以派生出多个具体产品类。
- 一个抽象工厂类,可以派生出多个具体工厂类。
- 每个具体工厂类可以创建多个具体产品类的实例,也就是创建的是一个产品线下的多个产品。
//抽象产品类 public abstract class Product{ public abstract void dosomething(); }
public class ProductA1 extends Product { @Override public void dosomething() { System.out.println("这是产品A1"); } }
//抽象工厂类 public abstract class Creater{ public abstract Product createProduceA(); public abstract Produce createProduceB(); }
public class Creater1 extends Creater { //创建产品A1 @Override public Product createProductA() { return new ProductA1(); } //创建产品B1 @Override public Product createProductB() { return new ProductB1(); } }
单例模式
单例对象(Singleton)是一种常用的设计模式。在 Java 应用中,单例对象能保证在一个 JVM中,该对象只有一个实例存在。这样的模式有几个好处:
1、某些类创建比较频繁,对于一些大型的对象,这是一笔很大的系统开销。
2、省去了 new 操作符,降低了系统内存的使用频率,减轻 GC 压力。
3、有些类如交易所的核心交易引擎,控制着交易流程,如果该类可以创建多个的话,系统完全乱了。(比如一个军队出现了多个司令员同时指挥,肯定会乱成一团),所以只有使用单例模式,才能保证核心交易服务器独立控制整个流程。
1、某些类创建比较频繁,对于一些大型的对象,这是一笔很大的系统开销。
2、省去了 new 操作符,降低了系统内存的使用频率,减轻 GC 压力。
3、有些类如交易所的核心交易引擎,控制着交易流程,如果该类可以创建多个的话,系统完全乱了。(比如一个军队出现了多个司令员同时指挥,肯定会乱成一团),所以只有使用单例模式,才能保证核心交易服务器独立控制整个流程。
常见的五种单例模式
主要:
饿汉式:线程安全,调用效率高,不能延时加载
懒汉式:线程安全,调用效率不高(synchronized),可以延时加载
其他:
双重检测锁式:由于JVM底层内部模型原因,偶尔会出现问题,不建议使用
静态内部类式:线程安全,调用效率高,延时加载
枚举单例:线程安全,调用效率高,不能延时加载
public class Singleton{ public static Singleton instance = null; //私有化静态实例,赋值null,实现延迟加载 private Singleton(){} //私有化构造方法,防止被实例化 public static synchronized Singleton getInstance(){ //懒汉式加载 //静态方法创建实例 if(instance == null){ instance = new Singleton(); } return instance; } } //如果该对象被序列化,可以保证对象在序列化前后保持一致 public Object readResolve(){ return instance; }上面所示,如果放入多线程环境下就会出现问题。所以我们首先会想到的是getInstance方法加入synchronized
public static synchronized Singleton getInstance(){ if(instance == null){ instance = new Singleton(); } return instance; }但是,synchronized 关键字锁住的是这个对象,这样的用法,在性能上会有所下降,因为每次调用 getInstance(),都要对对象上锁,事实上,只有在第一次创建对象的时候需要加锁,之后就不需要了,所以,这个地方需要改进。我们改成下面这个:
public static Singleton getInstance(){ if(instance == null){ //第一次创建才去加锁(双重检测) synchronized(instance){ if(instance == null){ instance = new Singleton(); } } } return instance; }将 synchronized 关键字加在了内部,也就是说当调用的时候是不需要加锁的,只有在 instance 为 null,并创建对象的时候才需要加锁,性能有一定的提升。但是,这样的情况,还是有可能有问题的,看下面的情况:在 Java 指令中创建对象和赋值操作是分开进行的,也就是说 instance = new Singleton();语句是分两步执行的。但是 JVM 并不保证这两个操作的先后顺序,也就是说有可能 JVM 会为新的 Singleton 实例分配空间,然后直接赋值给 instance 成员,然后再去初始化这个 Singleton 实例。这样就可能出错了,我们以进一步优化
private static class SingletonFactory{ //该类放置于单例类中,初始化的时候并不会执行静态内部类 private static Singleton instance = new Singleton(); public static Singleton getInstance(){ //当外部调用的时候采取初始化静态内部类 return SingletonFactory.instance; } }实际情况是,单例模式使用内部类来维护单例的实现,JVM 内部的机制能够保证当一个类被加载的时候,这个类的加载过程是线程互斥的。这样当我们第一次调用 getInstance 的时候,JVM 能够帮我们保证 instance 只被创建一次,并且会保证把赋值给 instance 的内存初始化完毕,这样我们就不用担心上面的问题。同时该方法也只会在第一次调用的时候使用互斥机制,这样就解决了低性能问题。这样我们暂时总结一个完美的单例模式 :
public class Singleton{ //私有构造方法,防止被实例化 private Singleton(){} //使用内部类维护单例 private static class SingletonFactory{ private static Singleton instance = new Singleton(); } //获取实例 public static Singleton getInstance(){ return SingletonFactory.instance; } //如果该对象被用于序列化,可以保证对象在序列化的前后一致 public Object readResolve(){ return getInstence(); } }以上如果在构造函数中抛出异常也会无法创造实例。所以实际问题中具体情况具体分析。
最后一种枚举实现单例,因为枚举是天然单例的。没有懒加载
public enum SingtonDemo{ //定义一个枚举的元素,它就代表了Singleton的一个实例 INSTANCE; public void singletonOperation(){ //功能处理 } } public class Test{ public static void main(String[] args){ SingletonDemo s1 = SingletonDemo.INSTANCE; SingletonDemo s2 = SingletonDemo.INSTANCE; System.out.println(s1==s2); } }
反射破解上述(除枚举外)单例模式
public class Client{ public static void main(String[] args){ Class clazz = Class.forName("单例模式的全类名"); Constructor c = clazz.getDeclaredConstructor(null);//获取构造器 c.setAccessible(true); SingletonDemo s1 = c.newInstance(); SingletonDemo s2 = c.newInstance(); //通过对比,两个创建的对象不是同一个。 //避免的方法:就是在单例类中的私有构造方法加上一个判断然后抛出异常 } }
反序列化破解
//单例类需要实现一个序列化接口 FileOutputStream fos = new FileOutputStream("硬盘文件名"); ObjectOutputStream oos = new ObjectOutputStream(fos); oos.writeObject(s1); //关闭上述对象 ObjectInputStream ois = new ObjectInputStream(new FileInputStream("硬盘文件名")); SingletonDemo s2 = ois.readObject();//获取实例 //做比较,发现序列化前后对象不同 //如何访问破解呢 //通过在单例类中定义一个readResolve()方法 //直接返回此方法的返回实例 private Object readResolve()throws ObjectStreamException{ read instance; }
建造者模式
将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示。
适用性:
- 当创建复杂对象的算法应该独立于该对象的组成部分以及它们的装配方式时
- 当构造过程必须允许被构造的对象有不同的表示时
public class Person { private String head; private String body; private String foot; public String getHead() { return head; } public void setHead(String head) { this.head = head; } public String getBody() { return body; } public void setBody(String body) { this.body = body; } public String getFoot() { return foot; } public void setFoot(String foot) { this.foot = foot; } }
//为创建一个对象的各个部件指定抽象接口 public interface PersonBuilder { void buildHead(); void buildBody(); void buildFoot(); Person buildPerson(); }
public class PersionDirector{ //接收PersionBuilder接口营造的对象,调用建造对象的建造方法,返回最终的建造出来的对象 public Person constructPerson(PersionBuilder pb){ pb.buildHead(); pb.buildBydy(); pb.buildFoot(); return pb.buildPerson(); } }
public class Man extends Person{ }
public class ManBuilder implements PersionBuilder{ Person person; public ManBuilder(){ person = new Man(); } public void buildBody(){ person.setBody("建造男人的身体"); } public void buildFoot(){ person.setFoot("建造男人的脚"); } public void buildHead(){ person.setHead("建造男人的头"); } public Person buildPerson(){ return person; } }Test类
public class Test{ //接收建造对象,建造一个具体的对象 PersionDirector pd = new PersonDirector(); //将建造对象的实例ManBuilder,传递给建造方法建造出实体对象 Person person = pb.constructPerson(new ManBuilder()); System.out.println(person.getBody()); System.out.println(person.getFoot()); System.out.println(person.getHead()); }
原型模式
该模式的思想就是将一个对象作为原型,对其进行复制、克隆,产生一个和原对象类似的新对象。
在 Java 中,复制对象是通过 clone()实现的,先创建一个原型类 :
在 Java 中,复制对象是通过 clone()实现的,先创建一个原型类 :
public class Prototype implements Cloneable{ public Object clone() throws CloneNotSupportedException{ Prototype proto = (Prototype)super.clone(); //直接调用Object的克隆 return proto; } }一个原型类,只需要实现 Cloneable 接口,覆写 clone 方法,此处 clone 方法可以改
成任意的名称,因为 Cloneable 接口是个空接口,你可以任意定义实现类的方法名,如 cloneA或者cloneB,因为此处的重点是super.clone()。super.clone()调用的是Object的clone()方法,而在 Object 类中,clone()是 native 的。
浅复制:将一个对象复制后,基本数据类型的变量都会重新创建,而引用类型,指向的还是原对象所指向的。
深复制:将一个对象复制后,不论是基本数据类型还有引用类型,都是重新创建的。简单来说,就是深复制进行了完全彻底的复制,而浅复制不彻底。
深复制:将一个对象复制后,不论是基本数据类型还有引用类型,都是重新创建的。简单来说,就是深复制进行了完全彻底的复制,而浅复制不彻底。
public class Prototype implements Cloneable,Serializable{ public static final long serialVersonUID = 1L; private String string; private SerializableObject obj; //浅复制 public Object clone() throws CloneNotSupportedException{ Prototype proto = (Prototype)super.clone(); return proto; } //序列化和反序列化进行深复制 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 setObje(SerializaableObject obj){ this.obj = obj; } }
class SerializableObject implements Serializable{ private static final long serialVersionUID = 1L; }要实现深复制,需要采用流的形式读入当前对象的二进制输入,再写出二进制数据对应的对象
public class Sheep implements Cloneable{ private String sname; private Date birthday; @Override protected Object clone() throws CloneNotSupportedException{ Object obj = super.clone(); //添加如下代码深复制 Sheep s = (Sheep)obj; s.birthday = (Date)this.birthday.clone();//把属性也进行克隆 return obj; } public String getSname(){ return sname; } }
public class Test{ public static void main(String[] args){ Sheep s = new Sheep("duoli",new Date()); //浅复制 Sheep s2 = s.clone(); } }