设计模式六大原则
- 单一职责
- 开放封闭
- 里氏替换原则
- 依赖倒置
- 迪米特原则
- 接口隔离原则
单例设计模式
- 饿汉式
public class Singleton{
private static Singleton instance = new Singleton();
private Singleton(){
}
public static Singleton getInstance(){
return instance;
}
}
这种方式在类创建时就完成了初始化,所以类加载慢,但是获取对象的速度快。
这种方式基于类加载机制,避免了多线程的同步问题。
在类加载的时候就完成初始化,没有达到懒加载的效果。如果从始至终未使用过这个实例,则会造成内存浪费
- 懒汉式
public Singleton{
private static Singleton instance;
private Singleton(){
}
public static Singleton getInstance(){
if(instance==null){
instance=new Singleton();
}
return instance;
}
}
懒汉模式声明了一个静态对象,在用户第一次调用时初始化,这虽然节约了资源,但第一次加载时需要实例化,反应稍慢一些,
而且在多线程时不能正常工作
- 懒汉式(线程安全)
public class Singleton {
private static Singleton instance;
private Singleton(){}
public static synchronized Singleton getInstance(){
if(instance==null){
instance=new Singleton();
}
return instance;
}
}
这种写法能够在多线程中很好地工作,但是每次调用getInstance方法时都需要进行同步,这会造成不必要的同步开销
而且大部分时候我们是用不到同步的,所以不建议用这种模式。
- 双重校验模式(DCL)
public class Singleton{
private volatile static Singleton instance;
private Singleton(){}
public static Singleton getInstance(){
if(instance==null){
synchronized(Singleton.class){
if(instance==null){
instance=new Singleton();
}
}
}
return instance;
}
}
这种写法在getInstance
方法中对Singleton进行了两次判空,第一次是为了不必要的同步,第二次是在Singleton等于null的情况下才创建实例。
在这里volatile会或多或少的影响性能,但考虑到程序的正确性,牺牲这点性能还是值得的。DCL的优点是资源利用率高。第一次执行genInstance
时单例对象才被实例化,效率高。其缺点是第一次加载时反应稍慢一些,在高并发环境下也有一定的缺陷。
DCL虽然在一定程度上解决了资源的消耗和多余的同步、线程安全等问题,但其还是在某些情况会出现失效的问题,这里建议用静态内部类单例模式来替代DCL。
- 静态内部类单例模式
public class Singleton{
private Singleton(){}
public static Singleton getInstance(){
return SingletonHolder.instance;
}
private static class SingletonHolder{
private static final Singleton instance =new Singleton();
}
}
第一次加载Singleton类时并不会初始化instance,只有第一次调用getInstance
方法时虚拟机加载SingletonHolder并初始化instance
这样不仅能确保线程安全,也能保证Singleton类的唯一性。所以,推荐使用静态内部类单例模式。
- 枚举单例
public enum Singleton{
INSTANCE;
public void doSomeThing(){}
}
默认枚举实例创建是线程安全的,并且在任何情况下都是单例。
上面讲的几种单例模式的实现中,有一种情况下其会重新创建对象,那就是反序列化:将一个单例实例对象写到磁盘再读回来,从而获得了一个实例。
反序列化操作提供了readResolve方法,这个方法可以让开发人员控制对象的反序列化。
在上面几个方法事例中,如果要杜绝单例对象被反序列化时重新生成对象,就必须加入如下方法:
private Object readResolve() throws ObjectStreamException{
return instance;
}
枚举单例的优点就是简单,其可读性并不是很高。
工厂方法模式
public abstract class Computer{
public abstract void start();
}
public abstract class ComputerFactory{
public abstract <T extends Computer> T createComputer(Class<T> clz);
}
public class GDComputerFactor extends ComputerFactory{
@Override
public <T extends Computer> T createComputer(Class<T> clz){
Computer computer =null;
String classname= clz.getName();
try{
computer =(Computer)Class.forName(classname).newInstance();
}catch(Exception e){
e.printStackTrace();
}
return (T)computer;
}
}
//客户端调用
public class Client{
public start void main(String[]args){
ComputerFactory computerFactory =new GDComputerFactor();
LenovoComputer mlenovoComputer = computerFactory.createComputer(LenovoComputer.class);
mlenovoComputer.start();
}
}
代理模式
- 静态代理
//抽象主题类
public interface IShop{
void buy();
}
//真实主题类
public class Merbng impelements IShop{
@Override
public void buy(){
System.out.println("购买");
}
}
//代理类
public class Purchasing impelements IShop{
private IShop mShop;
public Purchasing(IShop shop){
mShop=shop
}
@Override
public void buy(){
mShop.buy();
}
}
//客户端类
public class Client{
public static void main(String[] args){
IShop merbng =new Merbng();
IShop purchasing =new Purchasing(merbng);
purchasing.buy();
}
}
- 动态代理
public class DynamicPurchasing impelements InvocationHandler{
private Object obj;
public DynamicPurchasing(Object obj){
this.obj =obj;
}
@Override
public Object invoke(Object proxy,Method method,Object[] args)throws Throwable{
Object result = method.invoke(obj,args);
if(method.getName().equals("buy")){
System.out.println("在买买买");
}
return result;
}
}
在动态代理类中我们声明一个Object的引用,该引用指向被代理类,我们调用被代理类的具体方法在invoke()方法中执行。
接下来我们修改客户端类代码:
public class Client{
public start void main(String[]args){
//创建Merbng
IShop merbng =new Merbng();
//创建动态代理
DynamicPurchasing mDynamicPurchasing =new DynamicPurchasing(merbng);
//创建Merbng的ClassLoader
ClassLoader loader =merbng.getClass().getClassLoader();
//动态创建代理类
IShop purchasing=(IShop)Proxy.newProxyInstance(loader,new Class[]{IShop.class},mDynamicPurchasing);
purchasing.buy();
}
}
调用Proxy.newProxyInstance()来生成动态代理类,调用purchasing的buy方法会调用DynamicPurchasing的invoke方法。
代理模式的优点主要有以下几点:
- 真实主题类就是实现实际的业务逻辑,不用关心其他非本职的工作。
- 真实主题类随时会发生变化;但是因为它实现了公共接口,所以代理类可以不做任何修改就能够使用。
装饰者模式
//饮料抽象类
public abstract class Beverage{
String description = "Unknown Beverage";
public String getDescription(){
return description;
}
//cost方法是用来返回饮料的价钱(需在具体类中自己实现)
public abstract BigDecimal cost();
}
//深焙咖啡类(一种具体的饮料)
public class DarkRoast extends Beverage{
public DarkRoast(){
description="DarkRoast";
}
//实现cost方法,用来返回DarkRoast的价格
@Override
public BigDecimal cost(){
return new BigDecimal("3.00");
}
}
//低咖啡因咖啡类(一种具体的饮料)
public class Decaf extends Beverage{
public Decaf(){
description="Decaf"
}
//实现cost方法,用来返回Decaf的价格
@Override
public BigDecimal cost(){
return new BigDecimal("4.00");
}
}
//浓缩咖啡类(一种具体的饮料)
public class Espresso extends Beverage{
public Espresso(){
description="Espresso"
}
//实现cost方法,用来返回Espresso的价格
@Override
public BigDecimal cost(){
return new BigDecimal("2.00");
}
}
//调料装饰者抽象类(继承自饮料抽象类)
public abstract class CondimentDecorator extends Beverage{
//所有的饮料装饰者类都必须重新实现getDescription()方法。这样才能够用递归的方式来得到所选饮料的整体描述
public abstract String getDescription();
}
//摩卡调料类(继承自CondimentDecorator)
public class Mocha extends CondimentDecorator{
//用一个实例变量记录饮料,也就是被装饰者
Beverage beverage;
public Mocha (Beverage beverage){
this.beverage=beverage;
}
//在原来饮料的基础上添加上Mocha的描述(原来的饮料加入Mocha调料,被Mocha调料装饰)
@Override
public String getDescription(){
return beverage.getDescription() + ", Mocha";
}
//在原来饮料的基础上加上Mocha的价格
@Override
public BigDecimal cost(){
return new BigDecimal("0.2").add(beverage.cost());
}
}
//豆浆调料类(继承自CondimentDecorator)
public class Soy extends CondimentDecorator{
Beverage beverage;
public Soy(Beverage beverage){
this.beverage=beverage;
}
@Override
public String getDescription(){
reutrn beverage.getDescription()+",Soy";
}
@Override
public BigDecimal cost(){
return new BigDecimal("0.3").add(beverage.cost());
}
}
//奶泡调料类(继承自CondimentDecorator)
public class Whip extends CondimentDecorator{
Beverage beverage;
public Whip(Beverage beverage){
this.beverage=beverage;
}
@Override
public String getDescription(){
return beverage.getDescription()+"Whip";
}
@Override
public BigDecimal cost(){
return new BigDecimal("0.4").add(beverage.cost());
}
}
//咖啡馆(供应咖啡)
public class StarbuzzCoffee{
public start void main(String[] args){
//订一杯 Espresso(2.00)不需要调料,打印出描述和价钱
Beverage beverage=new Espresso();
System.out.println("Description:"+beverage.getDescription()+" $"+beverage.cost());
//制造出一个DarkRoast(3.00)对象,用Mocha(0.2)装饰它,用第二个Mocha(0.2)装饰它,用Whip(0.4)装饰它,打印出描述和价格
Beverage beverage2=new DarkRoast();
beverage2=new Mocha(beverage2);
beverage2=new Mocha(beverage2);
beverage2=new Whip(beverage2);
System.out.println("Description:"+ beverage2.getDescription()+"$"+beverage2.cost());
//再来一杯调料为豆浆(Soy 0.3)、摩卡(Mocha 0.2)、奶泡(Whip 0.4)的 Decaf(低咖啡因咖啡 4.0)。打印他的描述和价格
Beverage beverage3 =new Decaf();
beverage3=new Soy(beverage3);
beverage3=new Mocha(beverage3);
beverage3=new Whip(beverage3);
System.out.println("Description:"+beverage3.getDescription()+"$"+beverage3.cost());
}
}
运行结果:
Description: Espresso $2.00
Description: DarkRoast,Mocha,Mocha,Whip $3.9
Description: Decaf,Soy,Mocha,Whip $4.90
从以上,我们可以知道,当我们使用继承,导致子类膨胀,我们不想增加很多子类的情况下,将具体功能职责划分,同时继承装饰者超类,动态给一个对象添加一些额外的职责便实现了我们的装饰者模式。
- 优点:
装饰类和被装饰类可以独立发展,不会相互耦合,装饰模式是继承的一个替代模式,装饰模式可以动态扩展一个实现类的功能。 - 缺点:
多层修饰比较复杂
观察者模式
关于观察者模式这种发布-订阅的形式,我们可以拿微信公众号来举例。
假设微信用户就是观察者,微信公众号是被观察者,有多个微信用户关注了同一个公众号,
当这个公众号更新时就会通知这些订阅的微信用户,代码实现如下:
//抽象观察者 里面定义了 一个更新的方法
public interface Observer{
public void update(String message);
}
//具体观察者, 微信用户是观察者, 里面实现了更新的方法
public class WeiXinUser impelements Observer{
//微信用户名
private String name;
public WeiXinUser(String name){
this.name =name;
}
@Override
public void update(String message){
System.out.println(name+"-"+message);
}
}
//抽象被观察者 提供了attach、detach、notify 三个方法
public interface Subject{
//增加订阅者
public void attach(Observer observer);
//删除订阅者
public void detach(Observer observer);
//通知订阅者更新信息
public void notify(String message);
}
//具体被观察者,微信公众号就是具体被观察者,里面存储了订阅该公众号的微信用户,并实现了抽象主题中的方法
public class SubscriptionSubject impelements Subject{
//存储订阅公众号的微信用户
private List<Observer> weixinUserList = new ArrayList<Observer>();
@Override
public void attach(Observer observer){
weixinUserList.add(observer);
}
@Override
public void detach(Observer observer){
weixinUserList.remove(observer);
}
@Override
public void notify(String message){
for(Observer observer : weixinUserList){
observer.notify(message);
}
}
}
//客户端调用
public class Client{
public static void main(String[]args){
SubscriptionSubject mSubscriptionSubject =new SubscriptionSubject();
//创建微信用户
WeiXinUser user1 =new WeiXinUser("用户1");
WeiXinUser user1 =new WeiXinUser("用户2");
WeiXinUser user1 =new WeiXinUser("用户3");
//订阅公众号
mSubscriptionSubject.attach(user1);
mSubscriptionSubject.attach(user2);
mSubscriptionSubject.attach(user3);
//公众号更新发出消息给订阅的微信用户
mSubscriptionSubject.notify("有新文章了");
}
}
-
使用场景
关联行为场景,需要注意的是,关联行为是可拆分的,而不是“组合”关系
事件多级触发场景,跨系统的消息交换场景,如消息队列、事件总线的处理机制。 -
优点:
观察者和被观察者之间是抽象耦合,容易扩展,方便建立一套触发机制。 -
缺点:
子啊应用观察者模式时需要考虑一下开发效率和运行效率的问题。程序中包括一个被观察者、多个观察者,
开发、调试等内容会比较复杂,而且在Java中消息的通知一般是顺序执行的,那么一个观察者卡顿,会影响整体的执行效率,
在这种情况下一般会采用异步方式。
网友评论