设计模式的类型
- 创建型模式–>对象怎么来
- 结构型模式–>对象和谁有关
- 行为型模式–>对象与对象在干什么
序号 | 模式 & 描述 | 包括 |
---|---|---|
1 | 创建型模式 这些设计模式提供了一种在创建对象的同时隐藏创建逻辑的方式,而不是使用 new 运算符直接实例化对象。这使得程序在判断针对某个给定实例需要创建哪些对象时更加灵活 | 工厂模式(Factory Pattern)、抽象工厂模式(Abstract Factory Pattern)、单例模式(Singleton Pattern)、建造者模式(Builder Pattern)、原型模式(Prototype Pattern) |
2 | 结构型模式 这些设计模式关注类和对象的组合。继承的概念被用来组合接口和定义组合对象获得新功能的方式 | 适配器模式(Adapter Pattern)、桥接模式(Bridge Pattern)、过滤器模式(Filter Criteria Pattern)、组合模式(Composite Pattern)、装饰器模式(Decorator Pattern)、外观模式(Facade Pattern)、享元模式(Flyweight Pattern)、代理模式(Proxy Pattern) |
3 | 行为型模式 这些设计模式特别关注对象之间的通信 | 责任链模式(Chain of Responsibility Pattern)、命令模式(Command Pattern)、解释器模式(Interpreter Pattern)、迭代器模式(Iterator Pattern)、中介者模式(Mediator Pattern)、备忘录模式(Memento Pattern)、观察者模式(Observer Pattern)、状态模式(State Pattern)、空对象模式(Null Object Pattern)、策略模式(Strategy Pattern)、模板模式(Template Pattern)、访问者模式(Visitor Pattern) |
设计模式的七大原则
-
开闭原则(Open Close Principle)
对扩展开放,对修改关闭。在程序需要进行拓展的时候,不能去修改原有的代码,实现一个热插拔的效果。所以一句话概括就是:为了使程序的扩展性好,易于维护和升级。想要达到这样的效果,我们需要使用接口和抽象类
-
里氏代换原则(Liskov Substitution Principle)
子类可以扩展父类的功能,但不能改变原有父类的功能
任何基类可以出现的地方,子类一定可以出现。LSP 是继承复用的基石,只有当衍生类可以替换掉基类,软件单位的功能不受到影响时,基类才能真正被复用,而衍生类也能够在基类的基础上增加新的行为。里氏代换原则是对“开-闭”原则的补充。实现“开-闭” 原则的关键步骤就是抽象化。而基类与子类的继承关系就是抽象化的具体实现,所以里氏代换原则是对实现抽象化的具体步骤的规范
-
依赖倒转原则(Depecdence Inversion Principle)
开闭原则的基础,具体内容:针对接口编程,依赖于抽象而不依赖于具体
- 高层模块不应该依赖底层模块,两个模块都应该依赖抽象
- 抽象应该依赖于细节,细节应该依赖于抽象
-
接口隔离原则(Interface Segregation Principle)
使用多个隔离的接口,比使用单个接口要好。还是一个降低类之间的耦合度的意思,从这儿我们看出,其实设计模式就是一个软件的设计思想,从大型软件架构出发,为了升级和维护方便
- 客户端不应该依赖不需要它的接口
- 类之间的依赖关系应该建立在最小的接口上
eg:
接口一有1,2,3,4,5个方法,类A通过接口一依赖类B(只用到方法123),类C通过接口一依赖类D (只用到方法145)
则应该将接口一拆封:接口一(方法1)、接口二(方法23)、接口三(方法45)
-
迪米特法则(最少知道原则)(Demeter Principle)
一个实体应当尽量少的与其他实体之间发生相互作用,使得系统功能模块相对独立(降低类之间的耦合度)
如果两个类不必彼此直接通信,那么这两个类就不应发生直接的相互作用。如果其中一个类需要调用另一个类的某一个方法的话,可以通过第三者转发这个调用
-
合成复用原则(Composite Reuse Principle)
是尽量使用合成/聚合的方式,而不是使用继承
-
单一职责原则
- 降低类的复杂度,一个类只负责一项职责
- 提高类的可读性,可维护性
UML
类之间的关系
-
泛化关系(带空心箭头的直线)
继承非抽象类
-
实现关系(带空心箭头的虚线)
继承抽象类
-
聚合关系(带空心棱形箭头的直线)
表示实体对象之间的关系(弱依赖)
-
组合关系(带实心棱形箭头的直线)
表示实体对象之间的关系(强依赖)
-
关联关系(一条直线)
关联对象通常是以成员变量的形式实现的
-
依赖关系(一条虚线)
类构造方法及类方法的传入参数,箭头的指向为调用关系;依赖关系处理临时知道对方外,还是“使用”对方的方法和属性
组合与聚合的区别
组合和聚合的区别不是在形式上,而是在本质上
- 聚合关系(Aggregation)体现的是A对象可以包含B对象,但B对象不是A对象的组成部分。具体表现为,如果A由B聚合成,表现为A包含有B的全局对象,但是B对象可以不在A创建的时刻创建
- 组合关系(Composition):如果A由B组成,表现为A包含有B的全局对象,并且B对象在A创建的时刻创建
//组合实例
public class Person {
private Hand hand;
public Person() {
hand=new Hand();
}
private void go(){
hand.hand();
}
public static void main(String[] args) {
new Person().go();//person消亡时,hand也消亡
}
}
class Hand{
public void hand(){
System.out.print("hand");
}
}
//聚合实例
public class Person2 {
private Computer c;
public Person2(){
}
public Person2(Computer c){
this.c=c;
}
public void go(){
c.computer();
}
public static void main(String[] args) {
Computer computer=new Computer();
Person2 person = new Person2(computer);
person.go();//person消亡时,不影响computer
}
}
class Computer{
public void computer(){
System.out.print("computer");
}
}
工厂模式
工厂模式主要是为创建对象提供过渡接口,以便将创建对象的具体过程屏蔽隔离起来,达到提高灵活性的目的
简单工厂模式
建立一个工厂类,对实现了同一接口的一些类进行实例的创建
实例:Hibernate换数据库
优点:
- 一个调用者想创建一个对象,只要知道其名称就可以了
- 扩展性高,如果想增加一个产品,只要扩展一个工厂类就可以
- 屏蔽产品的具体实现,调用者只关心产品的接口
缺点:每次增加一个产品时,都需要增加一个具体类和对象实现工厂,使得系统中类的个数成倍增加,在一定程度上增加了系统的复杂度,同时也增加了系统具体类的依赖
eg:
public interface Sender{
public void Send();
}
---
public class SmsSender implements Sender{
@Override
public void Send(){
System.out.println("sms sender");
}
}
---
public class MailSender implements Sender{
@Override
public void Send(){
System.out.println("mail sender");
}
}
---
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.produce("sms");
sneder.Send();
}
}
工厂方法模式
定义一个用于创建对象的接口,让子类决定实例化哪一个类,工厂方法使得一个类的实例化延迟到其子类
是对简单工厂方法模式的改进,在简单工厂方法模式中,如果传递的字符串出错,则不能正确创建对象,而多个工厂方法模式是提供多个工厂方法,分别创 建对象
public class SendFactory{
public Sender produceMail(){
return new MailSender();
}
public Sender produceSms(){
return new SmsSender();
}
}
---
public class FactoryTest{
public static void main(String[] args){
SenderFactory factory = new SendFactory();
Sender sender = factory.produceMail();
sender.Send();
}
}
静态工厂方法
将多个工厂方法模式里的方法置为静态的,不需要创建实例,直接调用即可
public class SendFactory{
public static Sender produceMail(){
return new MailSender();
}
public static Sender produceSms(){
return new SmsSender();
}
}
public class FactoryTest{
Sender sender = SenderFactory.produceMail();
sender.Send();
}
总体来说,工厂模式适合:凡是出现了大量的产品需要创建,并且具有共同的接口时,可以通过工厂方法模式进行创建。在以上的三种模式中,第一种如果传入的字符串有误,不能正确创建对象,第三种相对于第二种,不需要实例化工厂类
抽象工厂模式
工厂方法模式有一个问题就是,类的创建依赖工厂类,也就是说,如果想要拓展程序,必须对工厂类进行修改,这违背了开闭原则
到抽象工厂模式,创建多个工厂类,这样一旦需要增加新的功能,直接增加新的工厂类就可以了,不需要修改之前的代码
public interface Sender{
public void Send();
}
---
public class MailSender implements Sender{
@Override
public void Send(){
System.out.println("mail sender")
}
}
---
public class SmsSender implements Sender{
@Override
public void Send(){
System.out.println("sms sender")
}
}
---
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 interface Provider{
public Sender produce();
}
---
public class Test{
public static void main(String[] args){
Provider provider = new SendMailFactory();
Sender sender = provider.produce();
sender.Send();
}
}
如果你现在想增加一个功能:发及时信息,则只需做一个实现类, 实现 Sender 接口,同时做一个工厂类,实现 Provider 接口,就 OK 了,无需去改动现成的代码。这样做,拓展性较好
总结:
-
简单工厂模式
- 工厂类角色、抽象产品角色、具体产品角色
-
工厂方法模式
-
抽象工厂角色、具体工厂角色、抽象产品角色、具体产品角色
-
-
抽象工厂模式
产品族: 位于不同产品等级结构中,功能相关联的产品组成的家族
抽象工厂模式的用意为:给客户端提供一个接口,可以创建多个产品族中的产品对象
- 抽象工厂角色、具体工厂角色、抽象产品角色、具体产品角色
代理模式
概念:代理模式是一种设计模式,提供了对目标对象额外的访问方式,即通过代理对象访问目标对象,这样可以在不修改原目标对象的前提下,提供额外的功能操作,扩展目标对象的功能
一个比方:在租房的时候,有的人会通过房东直租,有的人会通过中介租房
这里的中介就相当于代理,用户通过中介完成租房的一系列操作(看房、交押金、租房、清扫卫生)代理模式可以有效的将具体的实现与调用方进行解耦,通过面向接口进行编码完全将具体的实现隐藏在内部
分类:
静态代理:在编译时就已经实现,编译完成后代理类是一个实际的class文件
动态代理:在运行时动态生成的,即编译完成后没有实际的class文件,而是在运行时动态生成类字节码,并加载到JVM中
静态代理
使用方式:
创建一个接口,然后创建被代理的类实现该接口并且实现该接口中的抽象方法。之后再创建一个代理类,同时使其也实现这个接口。在代理类中持有一个被代理对象的引用,而后在代理类方法中调用该对象的方法
public interface UserDao{
void save();
}
---
public class UserDaoImpl implements UserDao{
@Ovveride
public void save(){
System.out.println("save");
}
}
---
public class TransactionHandler implements UserDao{
//目标代理对象
private UserDao target;
//构造代理对象时传入目标对象
public TransactionHandler(UserDao target){
this.target = target;
}
@Override
public void save(){
//调用目标方法前的处理
System.out,println("开启事务控制");
//调用目标对象的方法
target.save();
//调用目标方法后处理
System.out.println("关闭事务控制")
}
}
---
public class Main{
public static void main(String[] args){
//新建目标对象
UserDaoImpl target = new UserDaoImpl();
//创建代理对象,并使用接口对其进行引用
UserDao userDao = new TransactionHandler(target);
//针对接口进行调用
userDao.save();
}
}
使用JDK静态代理很容易就完成了对一个类的代理操作。但是JDK
静态代理的缺点也暴露了出来:由于代理只能为一个类服务,如果需要代理的类很多,那么就需要编写大量的代理类,比较繁琐
JDK动态代理
使用JKD动态代理的五大步骤:
- 通过实现InvocationHandler接口来自定义自己的InvocationHandler
- 通过Proxy.getProxyClass获得动态代理类
- 通过反射机制获得代理类的构造方法,方法签名为:getConstructor(InvocationHandler.class)
- 通过构造函数获得代理对象并将自定义的InvocationHandler实例对象作为参数传入
- 通过代理对象调用目标方法
public interface IHello{
void sayHello();
}
---
public class HelloImpl implements IHello{
@Override
public void sayHello(){
System.out.println("Hello world!")''
}
}
---
public class MyInvocationHandler implements InvocationHandler{
//目标对象
private Object object;
public MyInvocationHandler(Object target){
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable{
Systme.out.println("插入前置通知代码")
//执行相应的目标方法
Object rs = method.invoke(target, args);
System.out.println("插入后处理代码")
return rs;
}
}
---
public class MyProxyTest{
public static void main(String[] args)throws Exception{
//第一种
//1、生成$Proxy0的class文件
Systme.getProperties().put("com.file");
//2、获取动态代理类
Class proxyClazz = proxy.getProxyClass(IHello.calss.getClassLoader(),IHello.calss);
//3、获取代理类的构造函数,并传入参数类型InvocationHandler.class
Constuuctor constructor = proxyClazz.getConstructor(InvocationHandler.class);
//通过构造函数来创建动态的代理对象,将自定义的InvocationHandler实例传入
IHello iHello1 = (IHello)constructor.newInstance(new MyInvocationHandler(new HelloImlo()));
//5、通过代理对象调用目标方法
iHello.sayHello();
//第二种
//Proxy类中还有个将2~4步骤封装好的简便方法来创建动态代理对象
//其方法签名为:newProxyInstance(ClassLoader loader,Class<?>[] instance, InvocationHandler h)
//加载接口的类加载器
//一组接口
IHello iHello2 = (IHello)Proxy.newProxyInstance(IHello.class.getClassLoader(),new Class[]{IHello.class},new MyInvocationHandler(new HelloImpl()));
iHello2.sayHello();
}
}
JDK静态代理与JDK动态代理之间有些许相似,比如说都要创建代理类,以及代理类都要实现接口等
不同之处: 在静态代理中我们需要对哪个接口和哪个被代理类创建代理类,所以我们在编译前就需要代理类实现与被代理类相同的接口,并且直接在实现的方法中调用被代理类相应的方法;但是动态代理则不同,我们不知道要针对哪个接口、哪个被代理类创建代理类,因为它是在运行时被创建的
一句话来总结一下JDK静态代理和JDK动态代理的区别:
-
JDK静态代理是通过直接编码创建的,而
JDK
动态代理是利用反射机制在运行时创建代理类的 -
其实在动态代理中,核心是
InvocationHandler
。每一个代理的实例都会有一个关联的调用处理程序(InvocationHandler)。对待代理实例进行调用时,将对方法的调用进行编码并指派到它的调用处理器(InvocationHandler)的invoke
方法 -
对代理对象实例方法的调用都是通过InvocationHandler中的invoke方法来完成的,而invoke方法会根据传入的代理对象、方法名称以及参数决定调用代理的哪个方法
CGLIB
CGLIB包的底层是通过使用一个小而快的字节码处理框架ASM
,来转换字节码并生成新的类
CGLIB代理实现如下:
- 首先实现一个MethodInterceptor,方法调用会被转发到该类的intercept()方法
- 然后在需要使用的时候,通过CGLIB动态代理获取代理对象
public class HelloService {
public HelloService() {
System.out.println("HelloService构造");
}
---
//该方法不能被子类覆盖,CGLIB是无法代理final修饰的方法的(实现方式基于继承)
final public String sayOthers(String name) {
System.out.println("HelloService:sayOthers>>"+name);
return null;
}
---
public void sayHello() {
System.out.println("HelloService:sayHello");
}
}
---
public class MyMethodInterceptor implements MethodInterceptor{
/**
* sub:cglib生成的代理对象
* method:被代理对象方法
* objects:方法入参
* methodProxy: 代理方法
*/
@Override
public Object intercept(Object sub, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
System.out.println("======插入前置通知======");
Object object = methodProxy.invokeSuper(sub, objects);
System.out.println("======插入后者通知======");
return object;
}
}
---
public class Client {
public static void main(String[] args) {
// 代理类class文件存入本地磁盘方便我们反编译查看源码
System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "D:\\code");
// 通过CGLIB动态代理获取代理对象的过程
Enhancer enhancer = new Enhancer();
// 设置enhancer对象的父类
enhancer.setSuperclass(HelloService.class);
// 设置enhancer的回调对象
enhancer.setCallback(new MyMethodInterceptor());
// 创建代理对象
HelloService proxy = (HelloService)enhancer.create();
// 通过代理对象调用目标方法
proxy.sayHello();
}
}
JDK代理要求被代理的类必须实现接口,有很强的局限性
而CGLIB动态代理则没有此类强制性要求。简单的说,CGLIB
会让生成的代理类继承被代理类,并在代理类中对代理方法进行强化处理(前置处理、后置处理等)
总结一下CGLIB在进行代理的时候都进行了哪些工作
- 生成的代理类继承被代理类。在这里我们需要注意一点:如果委托类被final修饰,那么它不可被继承,即不可被代理;同样,如果委托类中存在final修饰的方法,那么该方法也不可被代理
- 代理类会为委托方法生成两个方法,一个是与委托方法签名相同的方法,它在方法中会通过
super
调用委托方法;另一个是代理类独有的方法 - 当执行代理对象的方法时,会首先判断一下是否存在实现了
MethodInterceptor
接口的CGLIB$CALLBACK_0
;,如果存在,则将调用MethodInterceptor
中的intercept
方法
在intercept
方法中,我们除了会调用委托方法,还会进行一些增强操作。在Spring AOP中,典型的应用场景就是在某些敏感方法执行前后进行操作日志记录
在CGLIB中,方法的调用并不是通过反射来完成的,而是直接对方法进行调用:通过FastClass机制对Class对象进行特别的处理,比如将会用数组保存method的引用,每次调用方法的时候都是通过一个index下标来保持对方法的引用
Fastclass机制
CGLIB采用了FastClass的机制来实现对被拦截方法的调用
FastClass机制就是对一个类的方法建立索引,通过索引来直接调用相应的方法
public class test10 {
//这里,tt可以看作目标对象,fc可以看作是代理对象;首先根据代理对象的getIndex方法获取目标方法的索引,
//然后再调用代理对象的invoke方法就可以直接调用目标类的方法,避免了反射
public static void main(String[] args){
Test tt = new Test();
Test2 fc = new Test2();
int index = fc.getIndex("f()V");
fc.invoke(index, tt, null);
}
}
class Test{
public void f(){
System.out.println("f method");
}
public void g(){
System.out.println("g method");
}
}
class Test2{
public Object invoke(int index, Object o, Object[] ol){
Test t = (Test) o;
switch(index){
case 1:
t.f();
return null;
case 2:
t.g();
return null;
}
return null;
}
//这个方法对Test类中的方法建立索引
public int getIndex(String signature){
switch(signature.hashCode()){
case 3078479:
return 1;
case 3108270:
return 2;
}
return -1;
}
}
上例中,Test2是Test的Fastclass,在Test2中有两个方法getIndex和invoke
在getIndex方法中对Test的每个方法建立索引,并根据入参(方法名+方法的描述符)来返回相应的索引
Invoke根据指定的索引,以ol为入参调用对象O的方法。这样就避免了反射调用,提高了效率
三种代理方式之间对比
代理方式 | 实现 | 优点 | 缺点 | 特点 |
---|---|---|---|---|
JDK静态代理 | 代理类与委托类实现同一接口,并且在代理类中需要硬编码接口 | 实现简单,容易理解 | 代理类需要硬编码接口,在实际应用中可能会导致重复编码,浪费存储空间并且效率很低 | 好像没啥特点 |
JDK动态代理 | 代理类与委托类实现同一接口,主要是通过代理类实现InvocationHandler并重写invoke 方法来进行动态代理的,在invoke方法中将对方法进行增强处理 | 不需要硬编码接口,代码复用率高 | 只能够代理实现了接口的委托类 | 底层使用反射机制进行方法的调用 |
CGLIB动态代理 | 代理类将委托类作为自己的父类并为其中的非final委托方法创建两个方法,一个是与委托方法签名相同的方法,它在方法中会通过super 调用委托方法;另一个是代理类独有的方法。在代理方法中,它会判断是否存在实现了MethodInterceptor 接口的对象,若存在则将调用intercept方法对委托方法进行代理 | 可以在运行时对类或者是接口进行增强操作,且委托类无需实现接口 | 不能对final 类以及final方法进行代理 | 底层将方法全部存入一个数组中,通过数组索引直接进行方法调用 |
CGLIB与JDK比较
- 使用CGLiB实现动态代理,CGLib底层采用ASM字节码生成框架,使用字节码技术生成代理类, 在jdk6之前比使用Java反射效率要高。唯一需要注意的是,CGLib不能对声明为final的方法进行代理, 因为CGLib原理是动态生成被代理类的子类
- 在jdk6、jdk7、jdk8逐步对JDK动态代理优化之后,在调用次数较少的情况下,JDK代理效率高于CGLIB代理效率。只有当进行大量调用的时候,jdk6和jdk7比CGLIB代理效率低一点,但是到jdk8的时候,jdk代理效率高于CGLIB代理,总之,每一次jdk版本升级,jdk代理效率都得到提升,而CGLIB代理消息确有点跟不上步伐
Spring选择代理方式
- 当Bean实现接口时,Spring就会用JDK的动态代理
- 当Bean没有实现接口时,Spring使用CGlib实现
- 可以强制使用CGlib
模板方法模式
定义一个操作中的算法的骨架,而将一些步骤延迟到子类中。模板方法使得子类可以不改变一个算法的结构即可重定义该算法的某些特定的步骤
特点:
- 模板方法模式是通过把不变行为搬移到超类,去除子类中重复的代码来体现他的优势
- 提供一个很好的代码复用平台
- 当不变的和可变的行为在方法的子类实现中混合在一起的时候,不变的行为就会在子类中重复出现,通过模板方法模式把这些行为搬移到单一的地方,这样就帮助子类摆脱重复不变的行为的纠缠
abstract class AbstractClass{
//一些抽象行为放到子类中去实现
public abstract void PrimitiveOperation1();
public abstract void PrimitiveOperation2();
//模板方法,给出逻辑的骨架,而逻辑的组成是一些相应的抽象操作,他们都推迟到子类实现
public void TemplateMethod(){
PrimitiveOperation1();
PrimitiveOperation2();
}
}
---
class ConcreteClassA extends AbstractClass{
@Override
public void PrimitiveOperation1(){
System.out.print("具体类A方法1实现");
}
@Override
public void PrimitiveOperation2(){
System.out.print("具体类A方法2实现");
}
}
---
class ConcreteClassB extends AbstractClass{
@Override
public void PrimitiveOperation1(){
System.out.print("具体类B方法1实现");
}
@Override
public void PrimitiveOperation2(){
System.out.print("具体类B方法2实现");
}
}
---
public static void Main(String[] args){
AbstractClass c;
c = new ConcreteClassA();
c.TemplateMethod();
c = new ConcreteClassB();
c.TemplateMethod();
}
eg:
package 设计模式.模板方法;
public class Plus extends Abstractcal {
@Override
public int cal(int num1, int num2) {
return num1 + num2;
}
}
---
package 设计模式.模板方法;
public abstract class Abstractcal {
public final int cal(String exp,String opt){
int array[] = split(exp,opt);
return cal(array[0],array[1]);
}
abstract public int cal(int num1,int num2);
public int[] split(String exp,String opt){
String array[] = exp.split(opt);
int[] arrayInt = new int[2];
arrayInt[0] = Integer.parseInt(array[0]);
arrayInt[1] = Integer.parseInt(array[1]);
return arrayInt;
}
}
---
package 设计模式.模板方法;
public class AbsTest {
public static void main(String[] args) {
String exp = "8+8";
Abstractcal abs = new Plus();
int result = abs.cal(exp,"\\+");
System.out.println(result);
}
}
eg:
package 设计模式.模板方法;
public abstract class Page {
public void PageAll(){
Question1();
Question2();
Question3();
}
public void Question1(){
System.out.println("第一道题:");
System.out.println("答案是:"+Answer1());
}
public void Question2(){
System.out.println("第二道题:");
System.out.println("答案是:"+Answer2());
}
public void Question3(){
System.out.println("第三道题:");
System.out.println("答案是:"+Answer3());
}
protected String Answer1(){
return "";
}
protected String Answer2(){
return "";
}
protected String Answer3(){
return "";
}
}
---
package 设计模式.模板方法;
public class PageA extends Page {
@Override
protected String Answer1() {
return "b";
}
@Override
protected String Answer2() {
return "b";
}
@Override
protected String Answer3() {
return "b";
}
}
---
package 设计模式.模板方法;
public class PageB extends Page {
@Override
protected String Answer1() {
return "a";
}
@Override
protected String Answer2() {
return "a";
}
@Override
protected String Answer3() {
return "a";
}
}
---
package 设计模式.模板方法;
public class PageTest {
public static void main(String[] args){
System.out.println("A学生的试卷");
Page pageA = new PageA();
pageA.PageAll();
System.out.println("B学生的试卷");
Page pageB = new PageB();
pageB.PageAll();
}
}
策略模式
定义了算法家族,分装起来,让他们之间可以相互替换,此模式让算法的变化,不会影响到使用算法的客户
- 在策略模式(Strategy Pattern)中,一个类的行为或其算法可以在运行时更改。这种类型的设计模式属于行为型模式
- 在策略模式中,我们创建表示各种策略的对象和一个行为随着策略对象改变而改变的 context 对象。策略对象改变 context 对象的执行算法
策略模式是一种定义一系列算法的方法,从该概念上看,所有这些算法完成的都是相同的工作,只是实现不同,它可以以相同的方式调用所有的算法,减少了各种算法类与使用算法类之间的耦合
- 消除了条件语句的使用,避免了多重条件判断
- 简化了单元测试,每个算法都有自己的类,可以通过自己的接口单独测试
缺点:
- 策略类会增多
- 所有的策略类都需要对外暴露
//抽象算法类
abstract class Strategy{
//算法方法
public abstract void AlgorithmInterface();
}
---
//具体算法A
class ConcreteStrategyA extends Strategy{
//算法A实现方法
@Override
public void AlgorithmInterface(){
System.out.print("算法A实现");
}
}
---
//具体算法B
class ConcreteStrategyA extends Strategy{
//算法B实现方法
@Override
public void AlgorithmInterface(){
System.out.print("算法A实现");
}
}
---
//具体算法C
class ConcreteStrategyA extends Strategy{
//算法C实现方法
@Override
public void AlgorithmInterface(){
System.out.print("算法A实现");
}
}
---
//算法使用环境
class Context{
Strategy strategy;
public Context(Strategy strategy){
this.strategy = strategy;
}
public void ContextInterface(){
strategy.AlgorithmInterface();
}
}
---
public static void main(String[] args){
Context context;
context = new Context(new ConcreteStrategyA);
context.ContextInterface();
context = new Context(new ConcreteStrategyB);
context.ContextInterface();
context = new Context(new ConcreteStrategyC);
context.ContextInterface();
}
eg:
package 设计模式.策略模式;
public class Plus extends AbstractCalculator implements ICalculator {
@Override
public int caculate(String exp) {
int arrayInt[] = split(exp,"\\+");
return arrayInt[0] + arrayInt[1];
}
}
---
package 设计模式.策略模式;
public abstract class AbstractCalculator {
public int[] split(String exp, String opt){
String array[] = exp.split(opt);
int arrayInt[] = new int[2];
arrayInt[0] = Integer.parseInt(array[0]);
arrayInt[1] = Integer.parseInt(array[1]);
return arrayInt;
}
}
---
package 设计模式.策略模式;
public class Plus extends AbstractCalculator implements ICalculator {
@Override
public int caculate(String exp) {
int arrayInt[] = split(exp,"\\+");
return arrayInt[0] + arrayInt[1];
}
}
---
package 设计模式.策略模式;
public class Mul extends AbstractCalculator implements ICalculator {
@Override
public int caculate(String exp) {
int arrayInt[] = split(exp, "\\*");
return arrayInt[0] * arrayInt[1];
}
}
---
package 设计模式.策略模式;
public class Sub extends AbstractCalculator implements ICalculator {
@Override
public int caculate(String exp) {
int arrayInt[] = split(exp,"-");
return arrayInt[0] - arrayInt[1];
}
}
---
package 设计模式.策略模式;
public class StrategyTest {
public static void main(String[] args) {
String exp = "2+8";
String exp2 = "2-8";
String exp3 = "2*8";
ICalculator cal = new Plus();
ICalculator cal2 = new Sub();
ICalculator cal3 = new Mul();
System.out.println(cal.caculate(exp));
System.out.println(cal2.caculate(exp2));
System.out.println(cal3.caculate(exp3));
}
}
单例模式
定义:保证一个类只有一个实例,并提供一个访问它的全局访问点
目的:控制特定的类只产生一个对象,允许在一定的情况下灵活的改变对象的个数
解决问题:一个全局使用的类频繁地创建和销毁
好处:
- 某些类创建比较频繁,对一些大型的对象,这是一笔很大的系统开销
- 省去了new操作符,降低了系统内存的使用频率,减轻GC的压力
- 有些类如交易所的核心交易引擎,控制着交易流程,如果该类可以创建多个的话,系统就乱了
单例对象能保证在一个JVM中,该对象只有一个实例存在
构造函数是私有的,不能使用构造函数来得到类的实例对象
实现方式:
-
饿汉式
public class Singleton{ //在自己内部定义自己一个实例 private static Singleton instance = new Singleton(); //私有的构造函数 private Singletion(){} //静态工厂方法,提供了一个供外部访问得到对象的静态方法 public static Singleton getInstance(){ return instance; }
-
懒汉式-双重检查-静态内部类
public class Singleton{ //赋值为null,目的是实现延迟加载 private static Singleton instance = null; private Singleton(){} //加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; }
Java指令中创建对象和赋值操作是分开进行的,也就是instance = new Singleton();语句是分两步执行的,但是JVM并不保证这两个操作的先后,也就是说有可能 JVM 会为新的 Singleton 实例分配空间,然后直接赋值给 instance 成员,然后再去初始化这个 Singleton 实例
eg:
- A、B线程同时进入第一个if判断
- A首先进行synchronized块,由于instance为null,所以它执行instance = new Singleton();
- 由于JVM内部的优化机制,JVM 先画出了一些分配给 Singleton 实例的空白内存,并赋值给 instance 成员(注意此时 JVM 没有开始初始化这个实例),然后 A 离开了 synchronized 块
- d>B 进入 synchronized 块,由于 instance 此时不是 null,因此它马上离开了 synchronized 块并将结果返回给调用该方法的程序
- 此时 B 线程打算使用 Singleton 实例,却发现它没有被初始化,于是错误发生了
//使用内部类 private static class SingletonFactory{ private static Singletion instance = new Singleton(); } public static Singleton getInstance(){ return SingletonFactory.instance; }
使用内部类来维护单例的实现,JVM 内部的机制能够保证当一个类被加载的时候,这个类的加载过程是线程互斥的。这样当我们第一次调用 getInstance 的时 候,JVM 能够帮我们保证 instance 只被创建一次,并且会保证把赋值给 instance 的内存初始化完毕,这样我们就不用担心上面的问题。同时该方法也只会在第一次调用的时候使用互斥机制,这样就解决了低性能问题
使用内部类会出现的问题:如果在构造函数中抛出异常,实例将永远得不到创建,也会出错
//只在创建类的时候进行同步 public class SignletonTest{ private static SingletonTest instance = null; private SingletonTest(){} private static synchronized void syncInit(){ if(instance == null){ instance = new SingletonTest(); } } public static SingletonTest getInstance(){ if(instance == null){ syncInit(); } return instance; } }
-
采用"影子实例"的办法为单例对象的属性同步更新
public class SingletonTest{ private static SingletonTest instance = null; private Vector properties = null; public Vector getProperties(){ return properties; } private SingletonTest(){} private static synchronized void syncInit(){ if(instance == null){ instance = new SingletonTest(); } } public static SingletonTest getInstance(){ if(instance == null){ syncInit(); } return instance; } public void updateProperties(){ //从文件或数据库中得到最新配置信息,并存放到properties属性中 SingletonTest shadow = new SingletonTest(); properties = shadow.getProperties(); } }
在更新属性时,直接生成另一个单例对象实例,这个新生成的单例对象实例将从数据库或文件中读取最新的配置信息;然后将这些配置信息直接赋值给旧单例对象的属性
-
枚举类
public enum Singleton{ //枚举元素本身就是单例 INSTANCE; public void singletonOperation(){ //添加自己需要的操作 } }
总结:
- 饿汉式:线程安全,调用效率高,但是不能延迟加载
- 懒汉式:线程安全,调用效率不高,但是能够延时加载
- Double CheckLock实现单例:由于JVM底层模型原因,会出现问题
- 静态内部类:线程安全,调用效率高,可以延时加载
- 枚举类:线程安全,调用效率高,不能延时加载,可以天然的防止反射和反序列化调用
使用选择:
- 单例对象占用资源少,不需要延时加载,枚举好于饿汉
- 单例对象占用资源多,需要延时加载,静态内部类好于懒汉式
延时加载的作用:
提高性能,不用将程序未使用到的功能都加载到内存,你用到的时候才加载,不用不加载到内存
主要是为了加快某些过程,让用户等待时间稍微短一些。 比如浏览器一般先加载文本,显示出来以后再加载图片。 这样用户不至于等到所有东西都下载完成以后再显示给用户
持久层框架像Hibernate时常默认延迟加载是因为在通常情况下数据库访问并且数据传送代价相当高昂。
延时加载合理使用可以避免CPU和内存高峰
适配器模式
定义:将一个类的接口转换成客户希望的另外 一个接口。Adapter 模式使得原本由于接口不兼容而不能一起工作的那些类可以一起工作
组成:目标角色、被适配角色(这个角色有一个已存在并使用了的接口,这个接口是我们需要适配的)、适配器角色(适配器器的核心,它将被适配角色已有的的接口转换为目标角色希望的接口)
分类:
- 类适配器模式:通过继承
- 对象适配器模式:通过组合
- 接口适配器
eg:
- 类适配器模式
核心思想就是:有一个 Source 类,拥有一个方法,待适配,目标接口是 Targetable,通过 Adapter 类,将 Source 的功能扩展到 Targetable 里
public class Source{
public void method1(){
Systme.out.println("method source");
}
}
---
public interface Targetable{
public void method1();
public void method1();
}
---
public class Adapter extends Source implements Targetable{
@Override
public void method2(){
System.out,println("method target");
}
}
---
public class AdapterTest{
public static void main(String[] args){
Targertable target = new Adapter();
target.method1();
target.method2();
}
}
- 对象的适配模式
基本思路和类的适配器模式相同,只是将 Adapter 类作修改,这次不继承 Source 类,而是 持有 Source 类的实例,以达到解决兼容性的问题
public class Wrapper implements Targetable{
private Source source;
public Wrapper(Source source){
super();
this.source = source;
}
@Override
public void method2(){
System.out.println("method target");
}
@Override
public void method1(){
source.method1();
}
}
---
public class AdapterTest{
public static void main(String[] args){
Source source = new Source();
Targetable target = new Wrapper(source);
target.method1();
target.method2();
}
}
- 接口的适配器模式
有时我们写的一个接口中 有多个抽象方法,当我们写该接口的实现类时,必须实现该接口的所有方法,这明显有时比 较浪费,因为并不是所有的方法都是我们需要的,有时只需要某一些,此处为了解决这个问 题,我们引入了接口的适配器模式,借助于一个抽象类,该抽象类实现了该接口,实现了所 有的方法,而我们不和原始的接口打交道,只和该抽象类取得联系,所以我们写一个类,继 承该抽象类,重写我们需要的方法就行
public interface Sourceable{
public void method1();
public void method2();
}
---
public abstract class Wrapper implements Sourceable{
public void method1(){}
public void method2(){}
}
---
public class SourceSub1 extends Wrapper{
public void method1(){
System.out.println("sub1 Source interface")
}
}
---
public class SourceSub2 extends Wrapper{
public void method2(){
System.out.println("sub2 Source interface")
}
}
---
public class WrapperTest{
public static void main(String[] args){
Sourceable source1 = new SourceSub1();
Sourceable source2 = new SourceSub2();
source1.method1();
source1.method2();
source2.method1();
source2.method2();
}
}
三种适配器应用场景
- 类的适配器模式:当希望将一个类转换成满足另一个新接口的类时,可以使用类的适配器模 式,创建一个新类,继承原有的类,实现新的接口即可
- 对象的适配器模式:当希望将一个对象转换成满足另一个新接口的对象时,可以创建一个 Wrapper 类,持有原类的一个实例,在 Wrapper 类的方法中,调用实例的方法就行
- 接口的适配器模式:当不希望实现一个接口中所有的方法时,可以创建一个抽象类 Wrapper, 实现所有方法,我们写别的类的时候,继承抽象类即可
主要解决:在软件系统中,常常要将一些"现存的对象"放到新的环境中,而新环境要求的接口是现对象不能满足的
注意事项:适配器不是在详细设计时添加的,而是解决正在服役的项目的问题
装饰模式
java中的io使用了装饰模式
定义:动态地给一个对象添加一些额外的职责(要求装饰对象和被装饰对象实现同一接口,装饰对象持有被装饰对象的实例)
组成:
- 抽象构建角色:定义一个接口,以规范准备接收附加责任的对象
- 具体构建角色:这是被装饰者,定义一个将要被装饰增加功能的类
- 装饰角色:持有一个构建对象的实例,并定义了抽象构建定义的接口
- 具体主装饰角色:负责给构建添加增添的功能
Source 类是被装饰类,Decorator 类是一个装饰类,可以为 Source 类动态的添加一些功能,
public interface Sourceable{
public void method();
}
---
public class Source implements Sourceable{
@Override
public void method(){
System.out.println("the origianl method")
}
}
---
public class Decorator implements Sourceable{
private Sourceable source;
public Decorator(Sourceable source){
super();
this.source = source; //代理模式则是在这new对象
}
@Override
public void method(){
System.out.println("before decorator");
source.method();
System.out.println("after decorator");
}
}
---
public class DecoratorTest{
public static void main(String[] args){
Sourceable source = new Source();
Sourceable obj = new Decorator(source);
obj.method();
}
}
应用场景:
- 需要扩展一个类的功能
- 动态的为一个对象增加功能,而且还能动态撤销
缺点:产生过多相似的对象,不易排错
外观模式
定义:为子系统中的一组接口提供一个一致的界面
组成:
- 门面角色:这是外观模式的核心,它被客户端调用,因此它熟悉子系统的功能,它内部根据客户角色已有的需求预定几种功能的组合
- 子系统角色:实现了子系统的功能
- 客户角色:调用门面角色来完成想要得到的功能
外观模式是为了解决类与类之间的依赖关系,降低了类之间的耦合度
关键代码:在客户端和复杂系统之间再加一层,这一层将调用顺序,依赖关系等处理好
public class CPU{
public void startup(){
System.out.println("cpu startup")
}
public void shutdown(){
System.out.println("cpu shutdown")
}
}
---
public class Memory{
public void startup(){
System.out.println("memory startup")
}
public void shutdown(){
System.out.println("memory shutdown")
}
}
---
public class Disk{
public void startup(){
System.out.println("disk startup")
}
public void shutdown(){
System.out.println("disk shutdown")
}
}
---
public class Computer{
private CPU cpu;
private Memory memory;
private Disk disk;
public Computer(){
cpu = new CPU();
memory = new Memory();
disk = new Disk();
}
public void startup(){
System.out.println("start computer");
cpu.startup();
memory.startup();
disk.startup();
System.out.println("start computer finishes")
}
public void shutdown(){
System.out.println("shutdown computer");
cpu.shutdown();
memory.shutdown();
disk.shutdown();
System.out.println("shutdown computer finishes")
}
}
---
public class User{
public static void main(Stirng[] args){
Computer computer = new Computer();
computer.startup();
computer.shutdown();
}
}
如果我们没有 Computer 类,那么,CPU、Memory、Disk 他们之间将会相互持有实例,产 生关系,这样会造成严重的依赖,修改一个类,可能会带来其他类的修改,这不是我们想要 看到的,有了 Computer 类,他们之间的关系被放在了 Computer 类里,这样就起到了解耦 的作用,
缺点:不符合开闭原则,如果要改东西很麻烦,继承重写都不适合
eg:facade模式的一个典型的应用就是进行数据库的连接,一般我们在每一次对数据库进行访问,都要进行以下操作:先得到 connect 实例,然后打开 connect 获得连接,得到一个 statement,执行 sql 语句进行查询,得到查询结果集
我们可以将这些步骤提取出来,封装在一个类里面。这样,每次执行数据库访问只需要 将必要的参数传递到这个类中就可以了
建造者模式
Java中的StringBuilder
定义:将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示(将构造复杂对象的过程和组成对象的部件解耦)
工厂模式提供的是创建单个类的模式,而建造者模式则是将各种产品集中起来进行管理,用来创建复合对象
建造模式着重于逐步将组件装配成一个成品并向外提供成品,而抽象工厂模式着重于得到产品族中相关的多个产品对象
组成:
- 抽象建造者角色:这个角色用来规范产品对象的各个组成部分的建造。一般而言,此角色独立于应用程序的业务逻辑
- 具体建造者:担任这个角色的是与应用程序紧密相关的类,它们在指导者的调用下创建实例产品。这个角色在实现抽象建造者角色提供的方法的前提下,达到完成产品组装,提供成品的功能
- 指导者角色:调用具体建造者角色以创建产品对象。指导者并没有产品类的具体知识,真正拥有产品类的具体知识的是具体建造者对象
- 产品角色:建造者中的复杂对象。它包含那些要定义组件的类,包括将这些组件装配成产品的接口
eg:
public class Builder{
private List<Sender> list = new ArrayList<Sender>();
public void produceMailSender(int count){
for(int i=0;i<count;i++){
list.add(new MailSender());
}
}
public void produceSmsSender(int count){
for(int i=0;i<count;i++){
list.add(new SmsSender);
}
}
}
---
public class Test{
public static void main(String[] args){
Builder builder = new Builder();
builer.produceMailSender(10);
}
}
eg:
public interface Item{
public String name;
public Packing packing();
public float price();
}
---
public interface Packing{
public String pack();
}
---
public class Wrapper implements Packing{
@Override
public String pack(){
return "Wrapper";
}
}
---
public class Bottle implements Packing{
@Override
public String pack(){
return "Bottle";
}
}
----
public abstract class Burger implements Item{
@Override
public Packing packing(){
return new Wrapper();
}
@Override
public abstaract float price();
}
---
public abstract class ColdDrink implements Item{
@Override
public Packing packing(){
return new Bottle();
}
@Override
public abstract flaot price();
}
---
public class VegBurger extends Burger{
@Override
public float price(){
return 25.0f;
}
@Override
public String name(){
return "Veg Burger";
}
}
---
public class ChickenBurger extends Burger{
@Override
public float price(){
return 50.5f;
}
@Override
public String name(){
return "Chicken Burger";
}
}
---
public class Coke extends ColdDrink {
@Override
public float price() {
return 30.0f;
}
@Override
public String name() {
return "Coke";
}
}
---
public class Pepsi extends ColdDrink {
@Override
public float price() {
return 35.0f;
}
@Override
public String name() {
return "Pepsi";
}
}
---
public class Meal{
private List<Item> items = new ArrayList<Item>();
public void addItem(Item item){
items.add(item);
}
public float getCost(){
for(Item item : items){
cost += item.price()
}
return cost;
}
public void showItems(){
for(Item item : items){
System.out.print("Item:"+item.name());
System.out.println(",Packing:"+item.packing().pcak());
System.out.println(",Price:"+item.price());
}
}
}
---
public class MealBuilder{
public Meal prepareVegMeal(){
Meal meal = new Meal();
meal.addItem(new VegBurger());
meal.addItem(new Coke);
return meal;
}
public Meal perpareNonVegMeal(){
Meal meal = new Meal();
meal.addItem(new ChickenBurger);
meal.addItem(new Pepsi());
return meal;
}
}
---
public class BuilderPatternTest{
public static void main(String[] args){
MealBuiler mealBuiler = new MealBuilder();
Meal vegMeal = mealBuilder.prepareVegMeal();
System.out.println("Veg Meal");
vegMeal.showItems();
System.out.println("Totle Cost:"+vegMeal.getCost());
Meal nonVegMeal = mealBuiler.prepareNonVegMeal();
System.out.println("\n\nNonVeg Meal");
nonVegMeal.showItems();
System.out.println("Total Cost:"+nonVegMeal.getCost());
}
}
eg:
public class Human {
private String head;
private String body;
private String hand;
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 getHand() {
return hand;
}
public void setHand(String hand) {
this.hand = hand;
}
public String getFoot() {
return foot;
}
public void setFoot(String foot) {
this.foot = foot;
}
}
---
public interface BuilderHuman {
void buildHead();
void buildBody();
void buildHand();
void buildFoot();
Human createHuman();
}
---
public class TallPersonBuilder implements BuilderHuman {
Human human;
public TallPersonBuilder() {
human = new Human();
}
@Override
public void buildHead() {
human.setHead("普通的头脑");
}
@Override
public void buildBody() {
human.setBody("壮实,高大的身体");
}
@Override
public void buildHand() {
human.setHand("长手");
}
@Override
public void buildFoot() {
human.setFoot("长脚");
}
@Override
public Human createHuman() {
return human;
}
}
---
public class SmartHumanBuilder implements BuilderHuman {
Human human;
public SmartHumanBuilder() {
human = new Human();
}
@Override
public void buildHead() {
human.setHead("高智商的头脑");
}
@Override
public void buildBody() {
human.setBody("健康的身体");
}
@Override
public void buildHand() {
human.setHand("普通的手");
}
@Override
public void buildFoot() {
human.setFoot("普通的脚");
}
@Override
public Human createHuman() {
return human;
}
}
---
public class HumanDirector {
public Human createHumanByDirector(BuilderHuman builderHuman){
builderHuman.buildHead();
builderHuman.buildBody();
builderHuman.buildHand();
builderHuman.buildFoot();
return builderHuman.createHuman();
}
}
---
public class BuilderPatternTest {
@Test
public void test() {
HumanDirector humanDirector = new HumanDirector();
//创建高个子的人
Human humanByDirector = humanDirector.createHumanByDirector(new TallPersonBuilder());
System.out.println(humanByDirector.getHead());
System.out.println(humanByDirector.getBody());
System.out.println(humanByDirector.getHand());
System.out.println(humanByDirector.getFoot());
System.out.println("-------------------");
//创建聪明的人
Human smartHuman = humanDirector.createHumanByDirector(new SmartHumanBuilder());
System.out.println(smartHuman.getHead());
System.out.println(smartHuman.getBody());
System.out.println(smartHuman.getHand());
System.out.println(smartHuman.getFoot());
}
}
优点:
- 建造者独立,易扩展
- 便于控制细节风险
缺点:
- 产品必须有共同点,范围有限制
- 如内部变化复杂,会有很多建造类
原型模式
定义:用原型实例指定创建对象的种类,并且通过拷贝这些原型创建新对象
组成:
- 客户角色:让一个原型克隆自己来得到一个新对象
- 抽象原型角色:实现了自己的clone方法,扮演这种角色的类通常是抽象类,且具有许多具体的子类
- 具体原型角色:被复制的对象,为抽象原型角色的具体子类
按照定义客户角色不仅要负责使用对象,而且还要负责对象原型的生成和克隆。这样造成客户角色分工就不是很明确,所以我们把对象原型生成和克隆功能单拿出来放到一个原型管理器中。原型管理器维护了已有原型的清单。客户在使用时会向原型管理器发出请求,而且可以修改原型管理器维护的清单。这样客户不需要编码就可以实现系统的扩展
//先new一个具体原型角色作为样本
Prototype p = new ConcretePrototype();
//使用原型p克隆出一个新对象p1
Prototype p1 = (Prototype)p.clone();
//使用原型管理器
Prototype p1 = PrototypeManager.getManager().getPrototype("ConcretePrototype");
//原型管理器只需要一个就够了,所以可以使用单例模式来实现控制
class PrototypeManager{
private static PrototypeManager pm;
private Map prototypes = null;
private PrototypeManager(){
prototypes = new HashMap();
}
//使用单例模式来得到原型管理器的唯一实例
public static PrototypeManager getManager(){
if(pm == null){
pm = new PrototypeManager();
}
return pm;
}
public void register(String name, Object prototype){
prototypes.put(name,prototype);
}
public void unregister(String name){
prototypes.remove(name);
}
public Prototype getPrototype(String name){
if(prototypes.containsKey(name)){
//将清单中对应得原型复制品返回给用户
return (Prototype)((Prototype)prototypes.get(name)).clone();
}else{
Prototype object = null;
try{
object = (Prototype)Class.forName(name).newInstance();
register(name, object);
}catch(Exception e){
System.out.println("Class"+name+"没有定义");
}
return object;
}
}
}
eg:在Java中复制对象是通过clone()实现的
public class Prototype implements Cloneable{
public Object clone() throws CloneNotSupportedException{
Prototype proto = (Prototype)super.clone();
return proto;
}
}
一个原型类,只需要实现 Cloneable 接口,覆写 clone 方法,此处 clone 方法可以改成任意的名称,因为 Cloneable 接口是个空接口,你可以任意定义实现类的方法名,如cloneA 或者 cloneB,因为此处的重点是 super.clone()这句话,super.clone()调用的是 Object的 clone()方法,而在 Object 类中,clone()是 native 的
-
浅复制:将一个对象复制后,基本数据类型的变量都会重新创建,而引用类型,指向的还是原对象所指向的
-
深复制:将一个对象复制后,不论是基本数据类型还有引用类型,都是重新创建的
public class Prototype implements Cloneable, Serializable{ private static final long serialVersionUID = 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 getObject(){ return obj; } public void setObj(SerializableObject obj){ this.obj = obj; } } class SerializableObject implements Serializable{ private static final long serialVersionUID = 1L; }
要实现深复制,需要采用流的形式读入当前对象的二进制输入,再写出二进制数据对应的对象
这种模式是实现了一个原型接口,该接口用于创建当前对象的克隆。当直接创建对象的代价比较大时,则采用这种模式。例如,一个对象需要在一个高代价的数据库操作之后被创建。我们可以缓存该对象,在下一个请求时返回它的克隆,在需要的时候更新数据库,以此来减少数据库调用
使用场景:
- 类初始化需要消化非常多的资源,这个资源包括数据、硬件资源等
- 性能和安全要求的场景
- 通过 new 产生一个对象需要非常繁琐的数据准备或访问权限,则可以使用原型模式
- 一个对象多个修改者的场景
- 一个对象需要提供给其他对象访问,而且各个调用者可能都需要修改其值时,可以考虑使用原型模式拷贝多个对象供调用者使用
桥接模式
Java AWT使用桥接模式
定义:将抽象部分与它的实现部分分离,使它们都可以独立的变化
组成:
- 抽象(Abstraction )角色:它定义了抽象类的接口而且维护着一个指向实现(Implementor)角色的引用
- 精确抽象(RefinedAbstraction)角色:实现并扩充由抽象角色定义的接口
- 实现(Implementor)角色:给出了实现类的接口,这里的接口与抽象角色中的接口可以不一致
- 具体实现(ConcreteImplementor)角色:给出了实现角色定义接口的具体实现
像我们常用的 JDBC 桥 DriverManager 一样,JDBC 进行连接数据库的时候,在各个数据库之间进行切换,基本不需要动太多的代码,甚至丝毫不用动,原因就是 JDBC 提供统一接口,每个数据库提供各自的实现,用一个叫做数据库驱动的程序来桥接就行了
public interface Sourceable{
public void method();
}
---
public class SourceSub1 implements Sourceable{
@Override
public void method(){
System.out.println("first sub");
}
}
---
public class SourceSub2 implements Sourceable{
@Override
public void method(){
System.out.println("second sub");
}
}
---
public abstract class Bridge{
private Sourceable source;
public void method(){
source.method();
}
public Sourceable getSource(){
return source;
}
public void setSource(Sourceable source){
this.source = source;
}
}
---
public class MyBridge extends Bridge{
public void method(){
getSource().method();
}
}
---
public class BridgeTest{
public static void main(String[] args){
Bridge bridge = new MyBridge();
//调用第一个对象
Sourceable source1 = new Sourceable1();
bridge.setSource(source1);
bridge.method();
//调用第二个对象
Sourceable source2 = new Sourceable2();
bridge.setSource(source2);
bridge.method();
}
}
eg:
人话就是如果你有3支不同笔尖的画笔,12种颜料,那么可以画出3 * 12种线条;而蜡笔想要做到同样的效果就需要36只
前面的画笔就是桥接模式(笔不同型号是一个变化维度,不同颜色的颜料是一个变化维度,两个维度不相互影响),
后面的画笔就是多重继承(笔型号和颜料一起影响蜡笔)
public abstract class Pen{
public abstract void draw(Color color);
}
---
public class PenOne extends Pen{
public void draw(Color color){
System.out.println("一号笔"+color.use()+"画画");
}
}
---
public class PenTwo extends Pen{
public void draw(Color color){
System.out.println("二号笔"+color.use()+"画画");
}
}
---
public interface Color{
public String use();
}
public class Blue implements Color{
public String use(){
return "蓝色";
}
}
---
public class Red implements Color{
public String use(){
return "红色";
}
}
---
public class User{
public static void main(String[] args){
Pen pen = new PenOne();
pen.draw(new Red());
}
}
使用场景:
- 如果一个系统需要在构件的抽象化角色和具体化角色之间增加更多的灵活性,避免在两个层次之间建立静态的继承联系,通过桥接模式可以使它们在抽象层建立一个关联关系
- 对于那些不希望使用继承或因为多层次继承导致系统类的个数急剧增加的系统,桥接模式尤为适用
- 一个类存在两个独立变化的维度,且这两个维度都需要进行扩展
优点:
- 抽象和实现的分离
- 优秀的扩展能力
- 实现细节对客户透明
缺点:桥接模式的引入会增加系统的理解与设计难度,由于聚合关联关系建立在抽象层,要求开发者针对抽象进行设计与编程
观察者模式
定义:观察者模式又名发布-订阅模式,定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新
组合:
- 抽象目标角色(Subject):目标角色知道它的观察者,可以有任意多个观察者观察同一个目标。并且提供注册和删除观察者对象的接口。目标角色往往由抽象类或者接口来实现
- 抽象观察者角色(Observer):为那些在目标发生改变时需要获得通知的对象定义一个更新接口。抽象观察者角色主要由抽象类或者接口来实现
- 具体目标角色(Concrete Subject):将有关状态存入各个 Concrete Observer 对象。当它的状态发生改变时, 向它的各个观察者发出通知
- 具体观察者角色(Concrete Observer):存储有关状态,这些状态应与目标的状态保持一致。实现 Observer 的更新接口以使自身状态与目标的状态保持一致。在本角色内也可以维护一个指向 Concrete Subject 对象的引用
public interface Observer{
public void update();
}
---
public class Observer1 implements Observer{
@Override
public void update(){
System.out.println("observer1 has received");
}
}
---
public class Oberver2 implements Observer{
@Override
public void update(){
System.out.println("observer2 has received");
}
}
---
public interface Subject{
public void add(Observer observer);
public void del(Observer observer);
public void notifyObserver();
public void operation();
}
---
public abstract class AbstractSubject implements Subject{
private Vector<Observer> vector = new Vector<Observer>();
@Override
public void add(Observer observer){
vector.add(observer);
}
@Override
public void del(Observer observer){
vector.remove(observer);
}
@Override
public void notifyObservers(){
Enumeration<Observer> enumo = vector.elements();
while(enumo.hasMoreElements()){
enumo.nextElement().update();
}
}
}
---
public class MySubject extends AbstractSubject{
@Override
public void operation(){
System.out.println("update self");
notifyObservers();
}
}
---
public class ObserverTest{
public static void main(String[] args){
Subject sub = new MySubject();
sub.add(new Observer1());
sub.add(new Observer2());
sub.operation();
}
}
使用场景:
- 一个抽象模型有两个方面,其中一个方面依赖于另一个方面。将这些方面封装在独立的对象中使它们可以各自独立地改变和复用
- 一个对象的改变将导致其他一个或多个对象也发生改变,而不知道具体有多少对象将发生改变,可以降低对象之间的耦合度
- 一个对象必须通知其他对象,而并不知道这些对象是谁
- 需要在系统中创建一个触发链,A对象的行为将影响B对象,B对象的行为将影响C对象……,可以使用观察者模式创建一种链式触发机制
注意事项:
- JAVA 中已经有了对观察者模式的支持类(Obserable类、Observer接口)
- 避免循环引用
- 如果顺序执行,某一观察者错误会导致系统卡壳,一般采用异步方式
优点:
- 观察者和被观察者是抽象耦合的
- 建立一套触发机制
缺点:
- 如果一个被观察者对象有很多的直接和间接的观察者的话,将所有的观察者都通知到会花费很多时间
- 如果在观察者和观察目标之间有循环依赖的话,观察目标会触发它们之间进行循环调用,可能导致系统崩溃
- 察者模式没有相应的机制让观察者知道所观察的目标对象是怎么发生变化的,而仅仅只是知道观察目标发生了变化
迭代器模式
定义:迭代器模式,又叫游标模式,提供一种方法访问一个容器对象中各个元素,而又不暴露该对象的内部实现细节
组成:
- 迭代器角色(Iterator):迭代器角色负责定义访问和遍历元素的接口
- 具体迭代器角色(Concrete Iterator):具体迭代器角色要实现迭代器接口,并要记录遍历中的当前位置
- 容器角色(Container):容器角色负责提供创建具体迭代器角色的接口
- 具体容器角色(Concrete Container):具体容器角色实现创建具体迭代器角色的接口——这个具体迭代器角色于该容器的结构相关
顺序访问聚集中的对象
public interface Collection{
public Iterator iterator();
public Object get(int i);
public int size();
}
---
public interface Iterator{
public Object previous();
public Object next();
public Object hasNext();
public Object first();
}
---
public class MyCollection implements Collection{
public String string[] = {"A","B","C","D","E"};
@Override
public Iterator iterator(){
return new MyIterator(this);
}
@Override
public Object get(int i){
return string[i];
}
@Override
public int size(){
return string.length;
}
}
---
public class MyIterator implements Iterator{
private Collection collection;
private int pos = -1;
public MyIterator(Collection collection){
this.collection = collection;
}
public Object previous(){
if(pos > 0){
pos--;
}
return colleciton.get(pos);
}
@Override
public Object next(){
if(pos < collection,size()-1){
pos++;
}
return collection.get(pos);
}
@Override
public boolean hasNext(){
if(pos < collection.size()-1){
return true;
}else{
return false;
}
}
@Override
public Object first(){
pos = 0;
return collection.get(pos);
}
}
---
public class Test{
public static void main(String[] args){
Collection collection = new MyCollection();
Iterator it = collection.iterator();
while(it.hasNext()){
System.out.println(it.next());
}
}
}
使用场景:
- 访问一个聚合对象的内容而无须暴露它的内部表示
- 需要为聚合对象提供多种遍历方式
- 为遍历不同的聚合结构提供一个统一的接口
优点:
- 它支持以不同的方式遍历一个聚合对象
- 迭代器简化了聚合类
- 在同一个聚合上可以有多个遍历
- 在迭代器模式中,增加新的聚合类和迭代器类都很方便,无须修改原有代码
缺点:由于迭代器模式将存储数据和遍历数据的职责分离,增加新的聚合类需要对应增加新的迭代器类,类的个数成对增加,这在一定程度上增加了系统的复杂性
责任链模式
使多个对象都有机会处理请求,从而避免请求的发送者和接收者之间的耦合关系。将这些对象连成一条链,并沿着这条链传递该请求,直到有一个对象处理它为止
适用范围:
- 有多个的对象可以处理一个请求,哪个对象处理该请求运行时刻自动确认
- 在不明确指定接收者的情况下,向多个对象中的一个提交一个请求
- 可动态指定一组对象处理请求
组成:
- 抽象处理者角色(Handler):它定义了一个处理请求的接口,当然对于子链的不同实现,也可以在这个角色中实现后继链
- 具体处理者角色(Concrete Handler):实现抽象角色中定义的接口,并处理它所负责的请求,如果不能处理则访问它的后继者
Abstracthandler 类提供了 get 和 set 方法,方便 MyHandle 类设置和修改引用对象,MyHandle 类是核心,实例化后生成一系列相互持有的对象,构成一条链
public interface Handler{
public void operator();
}
---
public abstract class AbstractHandler{
private Handler handler;
public Handler getHandler(){
return handler;
}
public void setHandler(Handler handler){
this.handler = handler;
}
}
---
public class MyHandler extends AbstractHandler implements Handler{
private String name;
public MyHandler(String name){
this.name = name;
}
@Override
public void operator(){
System.out.println(name+"deal");
if(getHandler()!=null){
getHandler().operator();
}
}
}
---
public class Test{
public static void main(String[] args){
MyHandler h1 = new MyHandler("h1");
MyHandler h2 = new MyHandler("h2");
MyHandler h3 = new MyHandler("h3");
h1.setHandler(h2);
h2.setHandler(h3);
h1.operator();
}
}
eg:
创建抽象类 AbstractLogger,带有详细的日志记录级别。然后我们创建三种类型的记录器,都扩展了 AbstractLogger。每个记录器消息的级别是否属于自己的级别,如果是则相应地打印出来,否则将不打印并把消息传给下一个记录器
public abstract class AbstractLogger{
public static int INFO = 1;
public static int DEBUG = 2;
public static int ERROR = 3;
protected int level;
protected AbstractLogger nextLogger;
public void setNextLogger(AbstractLogger next){
this.nextLogger = nextLogger;
}
public void logMessage(int level, String message){
if(this.level <= level){
write(message);
}
if(nextLogger != null){
nextLogger.logMessage(level, message);
}
}
abstract protected void write(String message);
}
---
public class ConsoleLogger extends AbstractLogger{
public ConsoleLogger(int level){
this.level = level;
}
@Override
protected void write(String message){
System.out.println("Standard Console::Logger:"+message);
}
}
---
public class ErrorLogger extends AbstractLogger{
public ErrorLogger(int level){
this.level = level;
}
@Override
protected void write(String message){
System.out.println("Error Console::Logger:"+message);
}
}
---
public class FileLogger extends AbstractLogger{
public FileLogger(int level){
this.level = level;
}
@Override
protected void write(String message){
System.out.println("File::Logger:"+message);
}
}
---
public class ChainPatterDemo{
private static AbstractLogger geChainOfLoggers(){
AbstractLogger errorLogger = new ErrorLogger(AbstractLogger.Error);
AbstractLogger fileLogger = new ErrorLogger(AbstarctLogger.DEBUG);
AbstractLogger consoleLogger = new ConsoleLogger(AbstractLogger.INFO);
errorLogger.setNextLogger(fileLogger);
fileLogger.setNextLogger(consoleLogger);
return errorLogger;
}
public static void main(String[] args){
AbstractLogger loggerChain = getChainOfLoggers();
loggerChain.logMessage(AbstractLogger.INFO,"this is an information");
loggerChain.logMessage(AbstractLogger.DEBUG,"this is a debug level information");
loggerChain.logMessage(AbstractLogger.Error,"this is an error information");
}
}
主要解决:职责链上的处理者负责处理请求,客户只需要将请求发送到职责链上即可,无须关心请求的处理细节和请求的传递,所以职责链将请求的发送者和请求的处理者解耦了
优点:
- 降低耦合度,它将请求的发送者和接收者解耦
- 简化了对象。使得对象不需要知道链的结构
- 增强给对象指派职责的灵活性,通过改变链内的成员或者调动它们的次序,允许动态地新增或者删除责任
- 增加新的请求处理类很方便
缺点:
- 不能保证请求一定被接收
- 系统性能将受到一定影响,而且在进行代码调试时不太方便,可能会造成循环调用
- 不容易观察运行时的特征,有碍于除错