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();
// ...
}
}