Java常用的设计模式

设计模式的目的

编写软件过程中,程序员面临着来自耦合性,内聚性 以及可维护性,可扩展性,重用性,灵活性 等多方面的挑战,设计模式是为了让程序(软件),具有更好的

代码重用性 (即:相同功能的代码,不用多次编写)
可读性 (即:编程规范性, 便于其他程序员的阅读和理解)
可扩展性 (即:当需要增加新的功能时,非常的方便,称为可维护)
可靠性 (即:当我们增加新的功能后,对原来的功能没有影响)
使程序呈现 高内聚,低耦合 的特性,

单例模式

所谓类的单例设计模式,就是采取一定的方法保证在整个的软件系统中,对某个类只能存在一个对象实例,并且该类只提供一个取得其对象实例的方法。

一、懒汉式:
也称单例模式,是一种需要的时候才创建对象的设计模式,优点是延迟加载,缺点是应用同步。如果在创建实例不加上Synchronized的话则会导致对象的访问不是线程安全的,换句话说就是在使用多线程访问单例模式的时候,(引申)是需要加同步函数,同步锁对象为当前类的类名.class

二、饿汉式:
线程安全模式,因为其在创建的同时就创建好一个静态的对象对象供系统使用,以后不再改变。饿汉有三个要素;
1、私有的构造方法。 2、指向自己实例的私有静态引用。 3、以自己实例为返回值的静态公有方法。

三 、双重检查(推荐使用)
Double-Check概念是多线程开发中常使用到的,如代码中所示,我们进行了两次if (singleton == null)检查,这样就可以保证线程安全了。
这样,实例化代码只用执行一次,后面再次访问时,判断 if(singleton == null),直接return实例化对象,也避免的反复进行方法同步.
线程安全; 延迟加载; 效率较高

四、静态内部类(推荐使用)
这种方式采用了类装载的机制来保证初始化实例时只有一个线程。
静态内部类方式在Singleton类被装载时并不会立即实例化,而是在需要实例化时,调用getlnstance方法,此时装载SingletonInstance类,从而完成Singleton的实例化。
类的静态属性只会在第一次加载类的时候初始化,所以在这里JVM帮助我们保证了线程的安全性,在类进行初始化时,别的线程是无法进入的。
优点:避免了线程不安全,利用静态内部类特点实现延迟加载,效率高

五、枚举(推荐使用)
这借助JDK1.5中添加的枚举来实现单例模式。不仅能避免多线程同步问题,而且还能防止反序列化重新创建新的对象。
这种方式是Effective Java作者Josh Bloch提倡的方式

工厂模式

简单工厂模式

简单工厂模式是属于创建型模式,是工厂模式的一种。简单工厂模式是由一个工厂对象决定创建出哪一种产品类的实例。简单工厂模式是工厂模式家族中最简单实用的模式
简单工厂模式: 定义了一个创建对象的类,由这个类来封装实例化对象的行为(代码)
在软件开发中,当我们会用到大量的创建某种、某类或者某批对象时,就会使用到工厂模式.

	/*
         简单工厂
         封装实例化对象的代码,专门用来实例化对象
         如果生成对象的逻辑有变化,则直接修改工厂类即可
    */
	public class SimpleFactory {
		// 外部通过SimpleFactory.creatPizza()获取pizza实例
		public static Pizza creatPizza(String name){
        	Pizza pizza = new Pizza(name);
        	return pizza;
    	}
	}    
	mian(){
		Pizza pizza = SimpleFactory.creatPizza();
	}

工厂方法模式

工厂方法模式: 定义了一个创建对象的抽象方法,由子类决定要实例化的类。工厂方法模式将对象的实例化推迟到子类。

	工厂方法模式
	将具体地创建逻辑下沉到子类,由子类来决定对象应该如何创建
 
	public abstract class OrderPizza {
	   // 抽象方法,由子类来决定具体地实现逻辑
	   abstract Pizza creatPizza();
	}
	
	public class OrderHamPizza extends OrderPizza{
	    @Override
	    Pizza creatPizza() {
	        return new Pizza("火腿");
	    }
	}
	
	public class OrderCheesePizza extends OrderPizza{
	   @Override
	   Pizza creatPizza() {
	       return new Pizza("芝士");
	   }
	}
	
	main(){
		// 创建火腿pizza
		OrderPizza op = new OrderHamPizza();
		Pizza hamPizza = op.creatPizza();
		
		// 创建芝士pizza
		op = new OrderCheesePizza();
		Pizza cheesePizza = op.creatPizza();
	}

抽象工厂模式

抽象工厂模式:定义了一个interface用于创建相关或有依赖关系的对象簇,而无需指明具体的工厂类
抽象工厂模式可以将简单工厂模式和工厂方法模式进行整合。
从设计层面看,抽象工厂模式就是对简单工厂模式的改进(或者称为进一步的抽象)
将工厂抽象成两层,AbsFactory(抽象工厂)和具体实现的工厂子类。程序员可以根据创建对象类型使用对应的工厂子类。这样将单个的简单工厂类变成了工厂簇, 更利于代码的维护和扩展。

    抽象工厂模式
    定义创建对象的接口,由子类工厂提供创建对象的具体逻辑
   	需要注意的是,当需使用工厂类时,使用者只需要聚合这个接口即可
   	
	public interface AbsFactory {
	    Pizza creatPizza();
	}
	
	火腿pizza工厂类
	public class HamPizzaFactory implements AbsFactory{
	    @Override
	    public Pizza creatPizza() {
	        return new Pizza("火腿");
	    }
	}
	
	芝士pizza工厂类
	public class CheesePizzaFactory implements AbsFactory{
	    @Override
	    public Pizza creatPizza() {
	        return new Pizza("芝士");
	    }
	}
	
	只需要聚合AbsFactory,即可使用所有的工厂子类
	即把单个简单工厂变成了工厂簇
	更利于代码的维护和扩展
	
	public class OrderPizza {
		
		这个工厂可能是火腿工厂,也可能是芝士工厂
		这就将单个简单工厂变成了很多个可选的工厂子类
		如果需要有新的工厂类,只需要实现AbsFactory即可使用
		
    	AbsFactory factory;

	    void setFactory(AbsFactory factory){
	        this.factory = factory;
	    }

	    Pizza order(){
	        return factory.creatPizza();
	    }

	    public static void main(String[] args) {
	        OrderPizza op = new OrderPizza();
	        op.setFactory(new HamPizzaFactory());
	        Pizza hamPizza = op.order();
	        
	        op.setFactory(new CheesePizzaFactory());
	        Pizza cheesePizza = op.order();
	    }
	}

工厂模式的意义
创建对象实例时,不要直接new,而是把这个new的动作放在一个工厂的方法中,并返回。有的书上说,变量不要直接持有具体类的引用。
将实例化对象的代码提取出来,放到一个类中统一管理和维护,达到和主项目的依赖关系的解耦。从而提高项目的扩展和维护性。
不要让类继承具体类,而是继承抽象类或者是实现接口(依赖倒转原则)
不要覆盖基类中已经实现的方法(里氏替换原则)。

原型模式

原型模式(Prototype模式)是指:用原型实例指定创建对象的种类,并且通过拷贝这些原型,创建新的对象
原型模式是一种创建型设计模式,允许一个对象再创建另外一个可定制的对象,无需知道如何创建的细节
工作原理是: 实现Cloneable接口后利用原型对象调用clone()方法,即可拷贝出新的对象
注意: clone()方法使用的是浅拷贝,对于对象中的基本数据类型的变量,是进行值传递,对于引用数据类型的变量(类,数组),也是值传递(引用传递),也就是说如果对某个对象中的引用数据类型的变量进行修改,则修改会在所有对象中生效。

	原型模式
	即想要克隆的对象类型需要实现 Cloneable 这个接口
	实现这个接口代表这个类能够克隆并且拥有克隆的能力
	当需要克隆的时候直接调用clone这个方法即可
	并且当对象的结构发生变化的时候,clone() 依旧是有效的
	
	@Data
	@ToString
	public class Sheep implements Cloneable{
	    private String name;
	    private Integer age;
	    
	    @Override
	    public Sheep clone() {
	        try {
	            Sheep clone = (Sheep) super.clone();
	            return clone;
	        } catch (CloneNotSupportedException e) {
	            throw new AssertionError();
	        }
	    }
    }
	
	main(){
		Sheep sheep = new Sheep();
        sheep.setAge(10);
        sheep.setName("小羊");
        Sheep[] sheeps = new Sheep[5];
        // 克隆五只一样的羊
        for(int i = 0; i < 5; ++i){
            sheeps[i] = sheep.clone();
        }
        for(Sheep s : sheeps) System.out.println(s);
	}

原型模式之深拷贝
复制对象的所有基本数据类型的成员变量值
为所有引用数据类型的成员变量申请存储空间,并完全复制该变量,直到所有的变量全部完成克隆。
深拷贝实现方式1: 重写clone方法来实现深拷贝
深拷贝实现方式2: 通过对象序列化实现深拷贝 (推荐),原型对象必须实现Serializable接口

	深拷贝之重写clone方法
	
	@Data
	@ToString
	public class Sheep implements Cloneable, Serializable{
	    private String name;
	    private Integer age;
	    private CloneTest test;
	    
	    @Override
	    public Sheep clone() {
	        try {
	            Sheep sheep = (Sheep) super.clone();
	            // 单独对引用数据类型进行拷贝
	            sheep.test = (CloneTest) test.clone();
	            return sheep;
	        } catch (CloneNotSupportedException e) {
	            throw new AssertionError();
	        }
	    }
    }
	
	
	class CloneTest implements Cloneable, Serializable{
		String text;
		@Override
    	protected Object clone() throws CloneNotSupportedException {
        	return super.clone();
    	}
	}
	
	
	深拷贝之反序列化
	    @Override
    public Sheep clone() {

        ByteArrayInputStream bis = null;
        ByteArrayOutputStream bos = null;
        ObjectInputStream ois = null;
        ObjectOutputStream oos = null;

        try {
            bos = new ByteArrayOutputStream();
            oos = new ObjectOutputStream(bos);
            oos.writeObject(this);

            bis = new ByteArrayInputStream(bos.toByteArray());
            ois = new ObjectInputStream(bis);
            return (Sheep) ois.readObject();
            
        } catch (Exception e) {
            throw new AssertionError();
        }finally {
            try {
                bis.close();
                bos.close();
                oos.close();
                ois.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
当需要创建新的对象且过程比较繁琐时,可以使用原型模式简化对象的创建方式,同时也可提高效率。
当使用原型模式创建对象时,无论对象结构如何变化,创建的对象总是符合当前结构。
当需要使用深拷贝时,可能会比较繁琐。

建造者模式

建造者模式(BuilderPattern)又叫生成器模式,是一种对象构建模式。它可以将复杂对象的建造过程抽象出来(抽象类别),使这个抽象过程的不同实现方法可以构造出不同表现(属性)的对象。
建造者模式是一步一步创建一个复杂的对象,它允许用户只通过指定复杂对象的类型和内容就可以构建它们,用户不需要知道内部的具体构建细节。

建造者模式的四个角色:
Product (产品角色):一个具体的产品对象。
Builder(抽象建造者):创建一个Product对象的各个部件指定的接口。
ConcreteBuilder(具体建造者):实现接口,构建和装配各个部件。
Director(指挥者):构建一个使用Builder接口的对象。它主要是用于创建一个复杂的对象。它主要有两个作用,一是:隔离了对象与对象的生产过程,二是:负责控制产品对象的生产过程。

适配器模式

适配器模式(Adapter Pattern)将某个类的接口转换成客户端期望的另一个接口表示,主的目的是兼容性,让原本因接口不匹配不能一起工作的两个类可以协同工作。其别名为包装器(Wrapper)
适配器模式属于结构型模式
主要分为三类: 类适配器模式、对象适配器模式、接口适配器模式
工作原理
适配器模式: 将一个类的接口转换成另一种接口.让原本接口不兼容的类可以兼容
从用户的角度看不到被适配者,是解耦的
用户调用适配器转化出来的目标接口方法,适配器再调用被适配者的相关接口方法
用户收到反馈结果,感觉只是和目标接口交互
类适配器

	目标:假设插孔电压220V, 手机充电需要5V, 利用类适配模式器将220V电压降至5V  

	输出220v的类
	public class Output220V {
	    public int output220V(){
	        return 220;
	    }
	}
	
	输出5V的接口
	public interface Output5V {
   		int output5V();
	}
	
	构建Adapter类让他继承Output220V 实现Output5V
	在其中完成适配,即让电压降至5V
	public class MyAdapter extends Output220V implements Output5V{
		@Override
	    public int output5V() {
	        return output220V() / 44;
	    }
	}

	main(){
		Output5V op = new MyAdapter();
		op.output5V();  // 输出 5v
	}

对象适配器 (常用)
基本思路和类的适配器模式相同,只是将Adapter类作修改,不是继承被适配类,而是持有被适配类的实例,以解决兼容性的问题。即:聚合被适配类,实现目标接口,完成适配
根据“合成复用原则”,在系统中尽量使用关联关系来替代继承关系。
对象适配器模式是适配器模式常用的一种

	目标:假设插孔电压220V, 手机充电需要5V, 利用类适配模式器将220V电压降至5V  
	其它的相较于类适配器来说都不用变,对于适配类来说用聚合关系替换继承关系即可
	
	构建Adapter类让他实现Output5V 并聚合一个Output220V
	在其中完成适配,即让电压降至5V
	public class MyAdapter implements Output5V{
	    private Output220V op220;
	
	    public MyAdapter(Output220V op220){
	        this.op220 = op220;
	    }
	
	    @Override
	    public int output5V() {
	        return op220.output220V() / 44;
	    }
	}

	main(){
		Output5V op = new MyAdapter(new Output220V());
		op.output5V();  // 输出 5v
	}

对象适配器和类适配器其实算是同一种思想,只不过实现方式不同。根据合成复用原则,使用组合替代继承,所以它解决了类适配器必须继承被适配者的局限性问题。
使用成本更低,更灵活。

接口适配器
注意事项

一些书籍称为:适配器模式(Default Adapter Pattern)或缺省适配器模式。
当不需要全部实现接口提供的方法时,可先设计一个抽象类实现接口,并为该接口中每个方法提供一个默认实现(空方法),那么该抽象类的子类可有选择地覆盖父类的某些方法来实现需求
适用于一个类不想实现一个接口所有方法的情况
简单来说,即利用抽象类来降低类和接口间的实现难度。一个类不用去实现一个接口所有的方法,因为在抽象类这一层,已经被实现了,子类只需要重写它需要的方法即可。

装饰者模式

装饰者模式: 动态的将新功能附加到对象上。在对象功能扩展方面,它比继承更有弹性,装饰者模式也体现了开闭原则(ocp)
简单来说,装饰者(Decorator)与被装饰者(Decorator / Coffee)同时继承一个抽象类(Drink),装饰者中聚合一个该抽象类(聚合一个被装饰者)。相当于装饰者套被装饰者,被装饰者继续套被装饰者。

策略模式

策略模式( Strategy Pattern)中,定义算法族,分别封装起来,让他们之间可以互相替换,此模式让算法的变化独立于使用算法的客户
这算法体现了几个设计原则,第一、把变化的代码从不变的代码中分离出来;第二、针对接口编程而不是具体类(定义了策略接口)﹔第三、多用组合/聚合,少用继承(客户通过组合方式使用策略)
当聚合了策略接口时,外部传入什么策略实例,聚合者就使用什么策略。

	策略接口
	public interface Speakable {
    	void speak();
	}
	
	策略接口
	public interface FlyAble {
    	void fly();
	}
	
	本来可以实现speak和fly接口,然后让子类自己定义这两个行为
	但是使用策略模式会更加灵活,子类也无需实现具体的方法
	当子类需要更换fly逻辑时,不需要修改源码,只需要传入一个fly接口的实例,即可重写定义fly方法的逻辑
	即外面传什么进来,鸭子就怎么飞
	
	对于具体的操作,得看聚合进来的策略实例	
	多使用关联代替继承、实现 —— 合成复用原则
	
	public abstract class Duck {
	
	    FlyAble flyAble;    // 飞翔策略接口
	    Speakable sa;       // 说话策略接口
	
	    void fly(){ 
	    	flyAble.fly();	// 调用策略实例的fly方法,即是否能飞翔完全取决于外部传入的策略
	    }
	
	    void speak(){
	        sa.speak();	// 调用策略实例的speak方法,即是否能说话完全取决于外部传入的策略
	    }
	
	    public void setFlyAble(FlyAble flyAble){
	        this.flyAble = flyAble;
	    }
	
	    public void setSa(Speakable sa) {
	        this.sa = sa;
	    }
	}

观察者模式

观察者模式简单来说就是 注册—通知,给定Subject和Observer,将Observer注册到对应的Subject中,然后通过Subject给所有注册的Observer发送消息。
观察者模式设计后,会以集合的方式来管理用户(Observer),包括注册,移除和通知。
这样,我们增加观察者(Observer),只需要注册进Subject即可,无需修改源码,遵守了ocp原则

代理模式

代理模式:为一个对象提供一个替身,以控制对这个对象的访问。即通过代理对象访问目标对象.这样做的好处是:可以在目标对象实现的基础上,增强额外的功能操作,即扩展目标对象的功能。
被代理的对象可以是远程对象、创建开销大的对象或需要安全控制的对象
代理模式有不同的形式,主要有三种静态代理、动态代理(JDK代理、接口代理)和cglib代理(可以在内存动态的创建对象,而不需要实现接口,他是属于动态代理的范畴)。
静态代理
静态代理在使用时,需要定义接口或者父类。被代理对象(即目标对象)与代理对象一起实现相同接口或者是继承相同父类

	代理对象与被代理对象一起实现的接口
	public interface ITeacherDao {
   		void teach();
	}
	
	代理类,聚合一个被代理对象
	public class TeacherDaoProxy implements ITeacherDao{

	    ITeacherDao dao; // 被代理对象
	
	    public TeacherDaoProxy(ITeacherDao dao){
	        this.dao = dao;
	    }
	
	    @Override
	    public void teach() {
	        System.out.println("进入代理方法");
	        dao.teach();	// 调用代理对象的方法, 可以再调用之前或者之后进行功能扩展
	        System.out.println("退出代理方法");
	    }
	}
	
	被代理类 
	public class TeacherDao implements ITeacherDao{
	    @Override
	    public void teach() {
	        System.out.println("调用了 teach 方法,开始教书");
	    }
	}
	
	通过调用代理对象的teach,完成对目标对象的调用
    public static void main(String[] args) {
        TeacherDao dao = new TeacherDao();
        ITeacherDao proxy = new TeacherDaoProxy(dao); // 代理teacherDao
		
		// 通过代理对象,调用目标方法
        proxy.teach();

        /*  控制台输出
            进入代理方法
            调用了 teach 方法,开始教书
            退出代理方法
         */
    }

在这里插入图片描述动态代理
代理对象,不需要实现接口,但是目标对象要实现接口,否则不能用动态代理
代理对象的生成,是利用JDK的API,动态的在内存中构建代理对象
动态代理也叫做:JDK代理、接口代理

	获取动态代理对象的工厂类
	public class ProxyFactory {
	
	    ITeacherDao teacherDao;
	
	    public ProxyFactory(ITeacherDao teacherDao){
	        this.teacherDao = teacherDao;
	    }
		
		利用java的反射机制,动态的在内存中构建代理对象
		
	    public Object getProxyInstance(){
	
	        /*
	            Proxy.newProxyInstance(ClassLoader loader,
	                                   Class<?>[] interfaces,
	                                   InvocationHandler h)
	            方法的三个参数:
	            loader 目标对象的ClassLoader, 利用固定的方法获取
	            interfaces 目标对象实现的接口, 利用固定的方法获取
	            InvocationHandler 事件处理 执行目标对象的方法时,会触发事件处理器,把目标对象的方法作为一个参数传入
	        */
	
	        return Proxy.newProxyInstance(teacherDao.getClass().getClassLoader(),
	                teacherDao.getClass().getInterfaces(),
	                new InvocationHandler() {
	                    @Override						// method 目标方法   args 调用目标方法时,传入的参数
	                    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
	                        System.out.println("进入动态代理方法");
	                        Object returnVal = method.invoke(teacherDao ,args); // 执行目标方法
	                        System.out.println("动态代理方法结束");
	                        return returnVal;
	                    }
	                });
        }
	}

	main(){
		TeacherDao dao = new TeacherDao();
        ProxyFactory pf = new ProxyFactory(dao);
        ITeacherDao proxy = (ITeacherDao) pf.getProxyInstance();

        proxy.teach();

        /*  控制台输出
            进入动态代理方法
            调用了 teach 方法,开始教书
            动态代理方法结束
        */
	}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值