今天分享几个常见的设计模式,这些设计模式是我在面经里面看到的。也就意味着公司在面试过程中常考的可能性相对较大,但是也不绝对。比起23种设计模式要全部记住似乎还是需要费一点功夫的。所以这里就本人认为的几个常见的设计模式进行相关资料的整理,方便复习和记忆。
单例模式
概念
java中单例模式是一种常见的设计模式,单例模式的写法有好几种,这里主要介绍二种:懒汉式单例、饿汉式单例。
单例模式有以下特点:
1、单例类只能有一个实例。
2、单例类必须自己创建自己的唯一实例。
3、单例类必须给所有其他对象提供这一实例。
代码实例
//懒汉式
public class Singleton {
// 需要加上volatile关键字来保证线程安全 对于双重检测的时候
private static volatile Singleton singleton = null;
private Singleton() {
System.out.println("init class");
}
// 第一种方法: 利用synchronize关键字来实现线程安全
public synchronized static Singleton getInstance() {
if (singleton == null) {
singleton = new Singleton();
}
return singleton;
}
// 第二种方法: 双重检测 来确保线程安全 同时将同步块放在方法内减少每次都需要同步加锁带来的消耗
public static Singleton getInstance1() {
if (singleton == null) {
synchronized (singleton) {
if (singleton == null) {
singleton = new Singleton();
}
}
}
return singleton;
}
// 第三种方法: 静态内部类实现懒汉式 实现了线程安全又避免同步带来的影响
private static class LazyLoader {
private static final Singleton SINGLETON = new Singleton();
}
public static final Singleton getInstance3() {
return LazyLoader.SINGLETON;
}
}
上面展现了三种方法来实现单例模式
1.第一种直接在方法上使用synchronize关键字 保证了线程安全,只是每次调用都需要同步 比较影响性能。而且单例模式下,创建的概率远低于使用返回实例的概率
- 第二种方法是双重检测 在返回实例方法的内部使用了synchronize代码块来实现线程安全,确保了实例为null时才会进行创建实例和同步的过程 ,避免了每次都需要同步带来的消耗
第二种方法存在一个问题
我们以A、B两个线程为例:
a>A、B线程同时进入了第一个if判断
b>A首先进入synchronized块,由于instance为null,所以它执行singleton = new Singleton();
c>由于JVM内部的优化机制,JVM先画出了一些分配给Singleton实例的空白内存,并赋值给instance成员(注意此时JVM没有开始初始化这个实例),然后A离开了synchronized块。
d>B进入synchronized块,由于instance此时不是null,因此它马上离开了synchronized块并将结果返回给调用该方法的程序。
e>此时B线程打算使用Singleton实例,却发现它没有被初始化,于是错误发生了。
简单来说 就是你new的对象并没有初始化完成,突然感觉有点神奇。new了不就初始化了吗?
这里可能涉及的就是JAVA比较底层的一些东西,可以把创建对象概况成三个步骤:
1。 分配对象内存空间
2。 初始化对象
3。 把对象指向它的内存地址
步骤2和3可能会发生指令重排序,不一定就是可能先执行3然后再2 所以就发生了上面的情况。这个时候可以考虑使用volatile关键字或者第三种方法去实现
- 第三种方法 是使用静态内部类,单例模式使用内部类来维护单例的实现,JVM内部的机制能够保证当一个类被加载的时候,这个类的加载过程是线程互斥的。这样当我们第一次调用getInstance的时候,JVM能够帮我们保证instance只被创建一次,并且会保证把赋值给instance的内存初始化完毕,这样我们就不用担心上面的问题。同时该方法也只会在第一次调用的时候使用互斥机制,这样就解决了低性能问题
使用场景
在计算机系统中,线程池、缓存、日志对象、对话框、打印机、显卡的驱动程序对象常被设计成单例
工厂方法模式和抽象工厂模式
概念
工厂模式(Factory Pattern)是 Java 中最常用的设计模式之一。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。
在工厂模式中,我们在创建对象时不会对客户端暴露创建逻辑,并且是通过使用一个共同的接口来指向新创建的对象。
按细的分一共有三种,一种是简单工厂,一种是工厂方法还有就是抽象工厂。GOF在《设计模式》一书中将工厂模式分为两类:
工厂方法模式(Factory Method)与抽象工厂模式(Abstract Factory)。
将简单工厂模式(Simple Factory)看为工厂方法模式的一种特例,两者归为一类。
代码实例
package com.Model.Factory;
//产品接口
public interface Sender {
public void send();
}
package com.Model.Factory;
//工厂模式
public class Factory {
// 创建相应的产品 根据产品类型
public Sender produceSender(String senderType) {
if (senderType == null) {
return null;
} else if (senderType.equals("sms")) {
return new SMSSender();
} else if (senderType.equals("email")) {
return new EmailSender();
} else {
System.out.println("invaild senderType....");
return null;
}
}
}
// 具体的EMail产品类 实现统一的产品接口
class EmailSender implements Sender {
@Override
public void send() {
System.out.println("sending a email...");
}
}
// 具体的SMS产品类 实现统一的产品接口
class SMSSender implements Sender {
@Override
public void send() {
System.out.println("sending a sms....");
}
}
下面是工厂方法模式的代码实例
只有工厂的部分有一些区别
// 工厂方法模式 避免字符串有误创建不了实例 以及不需要实例化工厂 可以直接调用创建实例
class SenderFactory {
public static Sender produceEmail() {
return new EmailSender();
}
public static Sender produceSMS() {
return new SMSSender();
}
}
工厂方法和简单工厂的一些区别就是 使用static以及不需要使用字符串来判断创建类型,避免创建null实例和可以不需要生成工厂对象直接调用生成具体产品实例的好处。
下面是抽象工厂模式
生成抽象工厂
package com.Model.Factory;
//抽象工厂 具有生产具体工厂的功能
public interface Producer {
public Sender provide();
}
具体的工厂
// 专门生成email的工厂
class EmailFactory implements Producer {
@Override
public Sender provide() {
return new EmailSender();
}
}
// 专门生产SMS的工厂
class SMSFactory implements Producer {
@Override
public Sender provide() {
return new SMSSender();
}
}
抽象工厂的好处就是可以不用修改原工厂的代码,直接进行拓展即可。比如我想再拓展一个电子发送器,则我只需要继承抽象工厂的接口,继承产品的接口。然后分别实现即可。
使用场景
方便地构造对象实例,而不必关心构造对象实例的细节和复杂过程。并且构造的实例过程比较复杂。
代理模式
概念
为其他对象提供一种代理以控制对这个对象的访问。在直接访问对象时带来的问题,比如说:要访问的对象在远程的机器上。在面向对象系统中,有些对象由于某些原因(比如对象创建开销很大,或者某些操作需要安全控制,或者需要进程外的访问),直接访问会给使用者或者系统结构带来很多麻烦,我们可以在访问此对象时加上一个对此对象的访问层。
代码实例
package com.Model.Proxy;
//买车行为
public interface BuyCar {
public void buycar();
}
package com.Model.Proxy;
public class People implements BuyCar {
private int cash;
private String vip;
private String username;
@Override
public void buycar() {
System.out.println(username + " is vip so he/she can buy any car...");
}
public int getCash() {
return cash;
}
public void setCash(int cash) {
this.cash = cash;
}
public String getVip() {
return vip;
}
public void setVip(String vip) {
this.vip = vip;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
}
// 代理类 去检测买车行为是否符合规则
class ProxyBuyCar implements BuyCar {
private People People;
public People getPeople() {
return People;
}
public void setPeople(People people) {
People = people;
}
@Override
public void buycar() {
if (People.getVip().equals("vip")) {
People.buycar();
} else if (People.getCash() >= 50000) {
System.out.println(People.getUsername() + "buy a new car trade over...");
} else {
System.out.println(People.getUsername() + "people can't buy a car ");
}
}
}
关键在于代理类,利用一些规则去限制行为的发生。
使用场景
1、Windows 里面的快捷方式。 2、猪八戒去找高翠兰结果是孙悟空变的,可以这样理解:把高翠兰的外貌抽象出来,高翠兰本人和孙悟空都实现了这个接口,猪八戒访问高翠兰的时候看不出来这个是孙悟空,所以说孙悟空是高翠兰代理类。 3、买火车票不一定在火车站买,也可以去代售点。 4、一张支票或银行存单是账户中资金的代理。支票在市场交易中用来代替现金,并提供对签发人账号上资金的控制。 5、spring aop。
说到代理模式 最经典的就是Spring AOP 就是对目标类和方法进行切面增强的功能,通过在目标类的基础上增加切面逻辑,完成一些和程序业务无关的内容。主要有两种实现模式: 一种是JDK的动态代理,一种是Cglib代理
这两种的区别是:
JDK动态代理只能代理实现了接口的目标类
Cglib则不需要 Cglib是基于类的代理 原理是对目标类生成一个子类然后覆盖和实现它的所有方法并增强 所有Cglib不能对final修饰的类进行代理。
这里提供一个JDK代理的实例
package com.Model.Proxy;
//操作定义
public interface SubjectOperations {
// 打印操作
public void print();
// 打印输入字符串操作
public void printfStr(String string);
}
package com.Model.Proxy;
public class RealSubject implements SubjectOperations {
@Override
public void print() {
System.out.println("我实现了接口 完成这个打印操作");
}
@Override
public void printfStr(String string) {
// TODO Auto-generated method stub
System.out.println("打印输入的内容: " + string);
}
}
package com.Model.Proxy;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
public class LogHandler implements InvocationHandler {
private Object ImpClass;
public LogHandler(Object realObject) {
this.ImpClass = realObject;
}
public Object bind(Object impclass) {
this.ImpClass = impclass;
return Proxy.newProxyInstance(impclass.getClass().getClassLoader(), impclass.getClass().getInterfaces(), this);
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("在调用代理的对象的真实方法前 我先调用一些自己的方法和规则..... 下面调用真实方法");
System.out.println("Method: " + method);
method.invoke(ImpClass, args);
System.out.println("调用代理的对象的真实方法后, 我进行一些逻辑处理 上面是调用的真实方法");
return null;
}
public static void main(String[] args) {
}
}
class Client {
public static void main(String[] args) {
RealSubject subject = new RealSubject();
LogHandler handler = new LogHandler(subject);
// 转化成接口 只能代理实现了接口的类
SubjectOperations pSubject1 = (SubjectOperations) handler.bind(subject);
System.out.println(pSubject1.getClass().getName());
pSubject1.print();
pSubject1.printfStr("YYYYY");
}
}
控制台输出:
com.sun.proxy.$Proxy0
在调用代理的对象的真实方法前 我先调用一些自己的方法和规则..... 下面调用真实方法
Method: public abstract void com.Model.Proxy.SubjectOperations.print()
我实现了接口 完成这个打印操作
调用代理的对象的真实方法后, 我进行一些逻辑处理 上面是调用的真实方法
在调用代理的对象的真实方法前 我先调用一些自己的方法和规则..... 下面调用真实方法
Method: public abstract void com.Model.Proxy.SubjectOperations.printfStr(java.lang.String)
打印输入的内容: YYYYY
调用代理的对象的真实方法后, 我进行一些逻辑处理 上面是调用的真实方法
职责链模式
概念
使多个对象都有机会处理请求,从而避免请求的发送者和接受者之间的耦合关系,将这个对象连成一条链,并沿着这条链传递该请求,直到有一个对象处理他为止。
代码实例
首先创建一个报销类,里面包含这个业务的一些具体信息 包括姓名 费用等
package com.Model.Chain_of_Responsibility;
public class MoneyRequest {
private String name;
private double money;
public MoneyRequest(String name, double money) {
super();
this.name = name;
this.money = money;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public double getMoney() {
return money;
}
public void setMoney(double money) {
this.money = money;
}
}
下面是一个抽象类 抽象的审批对象,处理报销请求
package com.Model.Chain_of_Responsibility;
public abstract class Leader {
protected String name;
protected Leader successor;
public Leader(String name) {
this.name = name;
}
public void Setsuccessor(Leader successor) {
this.successor = successor;
}
public abstract void handleRequest(MoneyRequest moneyRequest);
}
下面是具体的实现类,用于实现审批的条件和规则
package com.Model.Chain_of_Responsibility;
public class Director extends Leader {
public Director(String name) {
super(name);
// TODO Auto-generated constructor stub
}
public void handleRequest(MoneyRequest moneyRequest) {
if (moneyRequest.getMoney() < 300) {
System.out.println("主管 " + name + " 审批员工" + moneyRequest.getName() + " 的报销请求,请求金额为 "
+ moneyRequest.getMoney());
} else {
this.successor.handleRequest(moneyRequest);
}
}
}
package com.Model.Chain_of_Responsibility;
public class GeneralManager extends Leader {
public GeneralManager(String name) {
super(name);
// TODO Auto-generated constructor stub
}
public void handleRequest(MoneyRequest moneyRequest) {
if (moneyRequest.getMoney() < 2000) {
System.out.println("主管 " + name + " 审批员工 " + moneyRequest.getName() + " 的报销请求,请求金额为 "
+ moneyRequest.getMoney());
} else {
System.out
.println(moneyRequest.getName() + " 尽然敢报销这个金额 " + moneyRequest.getMoney() + " 不想混了,看来!");
}
}
}
package com.Model.Chain_of_Responsibility;
public class Manager extends Leader {
public Manager(String name) {
super(name);
// TODO Auto-generated constructor stub
}
public void handleRequest(MoneyRequest moneyRequest) {
if (moneyRequest.getMoney() < 800) {
System.out.println("主管 " + name + " 审批员工" + moneyRequest.getName() + " 的报销请求,请求金额为 "
+ moneyRequest.getMoney());
} else {
this.successor.handleRequest(moneyRequest);
}
}
}
使用场景
JAVA WEB 中 Apache Tomcat 对 Encoding 的处理,Struts2 的拦截器,jsp servlet 的 Filter。
网友评论