Java:常用设计模式

本文详细介绍了设计模式中的单例模式、工厂模式、适配器模式、组合模式及其应用场景。单例模式确保类只有一个实例,常用于全局配置或资源管理。工厂模式通过工厂类创建对象,降低了类间的耦合。适配器模式将不兼容的接口转换为可兼容的接口,实现类间通信。组合模式将对象组织成树形结构,方便对部分或全部对象进行操作。文章还讨论了这些模式在实际项目中的应用,如理财产品管理、多线程安全等,并提供了相应的代码示例。
摘要由CSDN通过智能技术生成

1.单例模式(Singleton Pattern)

概念上,即一个类只能有一个实例,而不允许创建多个类实例。应用于类的对应实体只能一个的情况下,比如学校校长,一个学校一般只有一个校长,所以不会在业务中new很多个校长实例,而是只创建一个校长实例进行操作。

设计上,一个singleton类跟普通类区别在于构造方法私有化,不允许外部调用构造方法,内部持有唯一的一个实例提供一个静态方法获取唯一实例。那么问题来了,不构造怎么实例化?于是我们的静态获取方法里还要提供 instance==null 的判断,如果 null 那就得先进行实例化,那么静态获取方法在第一次调用时就相当于“构造方法”了。

public class Singleton {
	private static Singleton instance;  //唯一实例
	private String pro;                 //类本身的普通成员变量例子

    //私有化的构造方法
	private Singleton(String pro) {
		this.pro = pro;
	}

    //静态获取方法
	public static Singleton getInstance(String pro) {
    //判断条件是多样的,可以根据业务需求修改
		if (instance == null) {
			instance = new Singleton(pro);
		}
		return instance;
	}

	public String getPro() {
		return pro;
	}

	public void setPro(String pro) {
		this.pro = pro;
	}
}

注意:在并发编程中,设想一下如果两个线程同时访问会有什么问题?

1.构造问题,多线程同时到达 if() 语句,此时如果未初始化,判断true,同时进入初始化语句,导致了多个实例被初始化,单例模式被破坏。所以我们需要对getInstance()进行锁控制,可以代码块lock(小粒度),也可以方法加synchronized(粒度较大,把整个类给锁了)

2.数据同步问题,因为单例允许在多个线程被同时访问时,相当于“共享资源”,所以要注意并发控制。

普通锁:效率较低(包括上面地非线程安全,都叫懒汉式,表示懒得提前实例化)

public static synchronized Singleton getInstance(){
    ......
    //或者不用synchronized,在这调lock
    //但是并发时不能保证数据同步,适用并发读场景
    if()
    //解锁
    ...... 
}

DCL:double checked locking,双重锁机制,实现跟synchronized锁住class的效果

public class Singleton {
	private volatile static Singleton instance;

	private Singleton() {
	}

	public static Singleton getInstance() {
    //lock
		if (instance == null) {
			instance = new Singleton();
		}
    //unlock
		return instance;
	}
}

饿汉式:即在声明时直接初始化实例,保证线程安全,但是浪费内存,不怎么用(默认线程都是饥饿的,等待访问)。

内部类控制实例加载:即饿汉式的升级版,利用内部类延时加载的规则,在Singleton中创建一个内部类专门用于生成,当没有访问时内部类不会加载,也就不会创建,当访问时内部类先加载(实例化Singleton)再返回实例,同样能保证线程安全。

public class Singleton {
	private Singleton() {
	}

    public class SingletonFactory{
        private static final SingleTon instance=new Singleton();
    }

	public static SingleTon getInstance(String pro) {
		return SingletonFactory.instance;
	}
}

枚举类:比较方便地实现单例控制,线程安全,枚举类本身就相当于单/多例模式 [dog-_-]

public enum Singleton{
    instance;
    //...控制都省了...
}

/**
*枚举类其实就是自动初始化实例,避免线程访问创建对象对单例的破坏
*所以,也可以理解为,则有点像饿汉式的单例实现
*
*另外的,枚举类在单例保护上,还有反序列化创建对象的安全性
*普通类是通过反射调用构造方法,new一个新对象,所以这也是对单例的破坏
*而枚举类的序列化仅仅是对name的序列化,反序列化得到的对象也只是根据对象name查找实例
*所以从这一层看,枚举能更大程度地提供线程安全
*/

另:多例模式

概念上,即特殊单例模式,应用于特定群体的实例数量控制,比如学校副校长只有n个,那么就需要控制副校长实例数量为n个。

设计上,采用List<T>、Set<T>等进行实例存放都行。具体的实例控制得看业务需求,比如实例之间允不允许属性重复......

代码跟单例模式类似,略。


2.工厂模式

概念上,即将类实例化的过程,看成工厂生产产品,由工厂类根据不同的需求,产生实际被调用类(产品),而非调用类自己new一个被调用类。既可以简化i f() 分支创建不同的类实例,也可以封装类创建功能,实现被调用类的透明化,解耦代码。

设计上,我们需要为被调用类xxx建立一个工厂类xxxFactory,然后调用者直接调用xxxFactory.getInstance()获得产品(被调用类xxx)。

public class Product {
	private String name;

	Product(String name) {
		this.name = name;
	}

	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}
}
public class Factory {

    //一般一个工厂也不会只有一个product
    //在创建“产品”实例时一般还会进行分类创建
	public Product getInstance(String name) {
		return new Product(name);
	}
}

实际上,工厂模式不仅上述的简单工厂模式(一厂多产品),因为一般会需要用到工厂都是因为有同一类product,也就是有product1、product2......所以把创建任务都放在一个Factory会导致耦合度过高。

真正的工厂模式(一厂一产品),设计上采用了由Factory接口产生Product接口(或父类)产品,实际操作由接口实现类xxxFactory产生xxxProduct的模式,保证代码耦合度较低。

public interface FactoryInterface {

	Product getProduct(String name);
}
public class FactoryOne implements FactoryInterface {
//FactoryTwo对应ProductTwo...

	@Override
	public Product getProduct(String name) {
		return new ProductOne(name);
	}
    //一个工厂只生产一个产品
}
//父类Product作为基本产品类,可以作为产品返回,也可以只是个“接口”
public class ProductOne extends Product {

	ProductOne(String name) {
		super(name);
		// 可以有新的成员变量
	}
	// 也添加新的方法
}

抽象工厂模式,即多厂每厂多产品或每厂一产品。假设一种情况,某个商品分为一、二级商品,然后内部又包括A,B类型,那么工厂应该怎么设置呢?也就是说我们初始化的时候面临4种情况:一级A,一级B,二级A,二级B,显然已有的工厂模式已经无法较好地去调用这种双层产品模型,所以我们有抽象工厂模型,由抽象工厂接口提供给调用类使用,实例化不同的工厂xxxFactory(相当于一层分支,产品为同一级),再由工厂生产产品类(二层分支,同级产品再分具体类型)。

public abstract class AbstractFactory {
	abstract Product getProduct(String name);
}
public class FactoryTwo extends AbstractFactory {
//FactoryOne,FactoryThree...对应二级,三级...

	@Override
	public Product getProduct(String name) {
		return new ProductA(name);
        //ProductB,ProductC...对应B类,C类...
	}
    //不同的工厂再根据不同的需要去创建产品
}


3.适配器模式

概念上,一个类不适用于当前环境,需要用一个适配类Adapter来进行适配连接,帮助完成当前环境对原功能类的调用。

设计上,分为类(继承)适配和方法(依赖)适配,即要么是继承功能类获得旧方法,加入新方法,完成功能转移;要么是直接写一个类进行原功能类的方法调用,处理结果后返回给当前环境调用。

写一个真的常用的适配模式吧,我们常常会需要排序,但是我们自己写的类一般是不支持调用sort(),因为没实现comparable接口,这个接口就可以认为是一个类适配器接口,实现了该接口就成了可调用的已适配类。

类适配

public class Someone implements Comparable<Someone> {
	private int id;

	Someone(int id) {
		this.id = id;
	}

	public int getId() {
		return id;
	}

	public void setId(int id) {
		this.id = id;
	}

	@Override
	public int compareTo(Someone o) {
        //传入参数代表了一个比较对象
        //大于时,返回正数代表升序,返回负数代表降序
        //可以理解为默认升序,如果你写的逻辑也是大于返回1,表示同意升序规则
		if (id > o.getId()) {
			return 1;
		}
		if (id < o.getId()) {
			return -1;
		}
		return 0;
        //其实也可以直接写成 retuen id-o.getId();
	}
}
import java.util.Arrays;

public class Main {

	public static void main(String[] args) {
		Someone[] arr = { new Someone(24), new Someone(93), new Someone(7) };
		Arrays.sort(arr);
	}
}

//排序结果7,24,93

当然你也可以适配Comparator,然后由xxxComparator作为适配器

public class Someone {
	private int id;

	Someone(int id) {
		this.id = id;
	}

	public int getId() {
		return id;
	}

	public void setId(int id) {
		this.id = id;
	}

	/*
	 * @Override public int compareTo(Someone o) { if (id > o.getId()) { return 1; }
	 * if (id < o.getId()) { return -1; } return 0; }
	 */
}
import java.util.Comparator;

public class SomeoneComparator implements Comparator<Someone> {

	@Override
	public int compare(Someone o1, Someone o2) {
        //来个降序的
		return -(o1.getId() - o2.getId());
	}
}
import java.util.Arrays;
import java.util.Comparator;

public class Main {

	public static void main(String[] args) {
		Someone[] arr = { new Someone(24), new Someone(93), new Someone(7) };
		Comparator c = new SomeoneComparator();
		Arrays.sort(arr, c);
	}
}

//排序结果93,24,7

方法适配,即直接调用已有功能类的方法即可,如A要调用B,但是直接调用,数据流A->B->A无法完成,那么就创建一个C来作为适配类。为了不让系统的类关系过于复杂,其实方法适配反而用得多,代码耦合度低。

数据流:
A->C,C处理为B能接收的格式
C->B
B->C,C再处理为A能接收的格式
C->A


4.组合模式

概念上,即将同体系的类实体存放在一个具有明显层次的组合体,同时使得每一层次上的“相同”对象既可以是单一对象,又可以是组合对象。

设计上,一般采用树结构。

看一种常见情况,一个部门里有很多人,主管是一级员工,经理是二级员工,实习生是三级员工,员工存在管理关系,那么如果我们独立创建类和实例,那么在操作上其实不好统一处理,比如遍历统计功能。

因此,我们将部门看作一棵树,每个员工看作节点,既有自己的数值,有可能是其它节点的父节点。一层节点:一级员工、同时包含管理二级员工集合;二层节点:二级员工、同时包含管理三级员工集合......整体上就构成了一个层次管理关系又有节点相似性,在部门事务的处理上便于统一进行,这就是组合模式。

public class Component {
	public String pro;  //类本身属性例子
	private List<Component> child = new ArrayList<Component>();  //子节点集

	public void add(Component c) {
		child.add(c);
	}

	public void delete(Component c) {
		child.remove(c);
	}

	public List<Component> getChild() {
		return child;
	}

	public String getPro() {
		return pro;
	}

	public void setPro(String pro) {
		this.pro = pro;
	}
}

上述是非安全模式,即所有类来自同一接口/类Component,其中封装了常用的统一模块操作,子类各自实现。为什么说不安全?因为如果一个节点已经没有child了,我们还保留了add()和delete()就会导致错误操作(当然在业务逻辑层子类肯定是实现为方法空)。

如果是安全模式,那么就是Component接口/类,没有child的操作,子类分为Leaf和Composite两类,Leaf无子节点操作,Composite再添加add()和delete()方法。这样做就能保证访问正确,但也造成了子节点不一致的问题。那么就需要我们区别对待叶子节点和非叶子节点,涉及到Composite就需要强转换Component=(Composite)instance;


5.桥接模式

概念上,即类实不用具体包含关系,而使用调用关系,即通过抽象层的桥接进行交互,达到包含效果。例子,类似于抽象工厂,有A、B工厂,生产一级、二级产品,如果我们需要增加三级产品,那就意味着A、B都得根据实际情况去添加三级产品的生产方法,显然有多个工厂时就很麻烦了。所以,我们把相同层次的类抽离出来,例把*级产品抽离出来,使得工厂和产品独立存在,两个层次的体系可以独立变化,又能通过抽象层的连接进行交互。

设计上,不同层次类都继承/实现独立接口/父类,面向接口/抽象,各层次只存在抽象交互,而不存在具体包含。

public abstract class Factory {
	private Product pro;  //工厂拥有抽象产品实例

	public abstract Product getProduct(int proId);
    //子类实现方法,方法中调用使工厂获得产品实例
    //而非工厂本身包含具体实例,达到产品变化不影响工厂
}
public abstract class Product {

	public abstract void behave();
}


6.访问者模式

概念上,数据和数据操作分离,适用于操作多变的情况。

设计上,提供DataObject作为数据体,数据体DataObject提供外部访问接口,但是自己不操作,而是调用Operation访问DataObject,DataObject再将数据提交给操作方Operation完成处理。

举个实际例子:A工厂产品不变,拥有多种展示产品的需求,那么我们是否每一次展示都得改一下show()呢?如果我们有一个Operation接口,每一次我们只需实现Operation接口,形成不同的实现类,就可以拥有不同show()方法,而我们不同的调用只需传入不同参数即可,就可以很高效地实现“数据操作扩展”了。

public class DataObject {

	public void accept(Operation o) {
		o.visit(this);
		// 回传实例,即提供访问数据的接口
	}
}
public class Operation {

	public void visit(DataObject o) {
		// 对象的实际访问操作,由不同子类实现不同的访问操作
        //比如在工厂的show功能就可以在此重写
	}
}

----------

7.观察者模式

即被观察方维护一个List<观察者>,当自身出现数据变换时通知观察者,以便观察者做出相应改变。可以理解为自动被监听。

缺点是仅仅是通知,无法发送确切的更改信息(因为事实上你也很难去知道哪个观察者需要知道什么信息,所以通知时常常是不必要的通知),还需要观察者自动查询更改。注意不要死循环了,A观察B,B观察A,就可能会导致A变通知了B,B跟着变又通知了A......

实现上,自己为每个观察者设计一个updateState()就是了...

8.代理模式

代理模式的定义:为其他对象提供一种代理以控制对这个对象的访问。在某些情况下,一个对象不适合或者不能直接引用另一个对象,而代理对象可以在客户端和目标对象之间起到中介的作用。

java一般分为静态代理和动态代理,静态即代理类是跟原类一样先编译好的,使用过程直接调用,动态代理即在调用过程中再手动生成一个代理类去操作。


9.一个应用场景

有一个网页<有好几种理财产品介绍,产品信息类似。由于信息少更改,查询量也小,可以使用单例模式,避免过多进行DB操作。

public Class Gold implements FinancialProduct {

    public static Gold gold;
    
    public static FinancialProduct getProduct(){
        return GoldFactory.gold;
    }

    public Class GoldFactory(){
        public static Gold gold = new Gold();
    }

    private Gold(){
        // get the gold message
    }

    //...其它方法和属性

}

产品调用可以使用工厂模式,避免在业务service层使用太多if分支。

public Class FinancialFactory {
    // ...
        
    // 获得不同的理财产品信息。
    public FinancialProduct getFinancialProduct(String type){
        if(Const.GOLD.equals(type){
            return Gold.getProduct();
        } else if(Const.TIMEDEPOSIT.equals(type){
            return TimeDeposit.getProduct();
        } else {
            return null;
        }
    }

}

由于信息更改较少,其它功能的使用者需要更新信息时进行通知,理财产品类设置一个更新方法,接收更新通知,完成信息的DB更新和Object更新。即观察者模式。

public Class Gold implements FinancialProduct {
    // ...

    public synchronized void upState(FinancialProduct financialProduct){
        if(...){
            // modify message
        }
    }

}

如果是不同的系统,最好是加多一层代理,可以屏蔽业务逻辑,处理交互数据。

public Class GoldProxy implements FinancialProduct{
    // ...
    private Gold gold = Gold.getProduct();
    
    public void upState(FinancialProduct financialProduct){
        // ...处理一些系统交互数据
        gold.upState();
        // ...
    }

}

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值