面试时常见的就是的就是让你手写一个单例模式(注意单例模式的几种不同的实现方法)或者让你说一下某个常见的设计模式在你的项目中是如何使用的,另外面试官还有可能问你抽象工厂和工厂方法模式的区别、工厂模式的思想这样的问题。
建议把代理模式、观察者模式、(抽象)工厂模式好好看一下,这三个设计模式也很重要。
1.手写一个单例模式(懒汉模式和饿汉模式)所谓的懒汉就是懒加载,饿汉就是即时加载
懒汉模式:线程不安全需要加锁
package thread.example;
//单线程的懒汉模式
public class LazySingle {
private static LazySingle instance = null;
//只有在调用该方法的时候才实例化
public static LazySingle getInstance() {
if(instance == null) {
instance = new LazySingle();
}
return instance;
}
}
package thread.example;
public class LazySingle {
//多线程安全下的懒汉模式
private LazySingle() {}
//使用 volatile 可以禁止 JVM 的指令重排,保证在多线程环境下也能正常运行。
private static volatile LazySingle instance = null;
//只有在调用该方法的时候才实例化
public static LazySingle getInstance() {
//先判断对象是否已经实例过,没有实例化过才进入加锁代码
if (instance == null) {
//类对象加锁
synchronized (LazySingle.class) {
if (instance == null) {
instance = new LazySingle();
}
}
}
return instance;
}
}
饿汉模式:线程安全
package thread.example;
//饿汉模式
public class HungrySingle {
//在类加载的时候就实例化了,类加载只有一次,所以值实例化出了一份该实例对象
private static HungrySingle instance = new HungrySingle();
public static HungrySingle getInstance() {
return instance;
}
}
2.说一下某个常见的设计模式在你的项目中是如何使用的
Singleton:创建型模式,负责创建维护一个全局唯一实例
Factory:创建型模式,对象工厂负责根据标识创建或获取具体的实例对象
Strategy:行为型/运行时模式,策略负责根据标识控制应用运行时的行为
3.抽象工厂和工厂方法模式的区别
1> 定义:
工厂方法模式:定义用户创建对象的接口,让子类决定实例化那一个类。
抽象工厂模式:提供一个创建 一系列相关或相互依赖对象的接口,而无需制定他们的具体的类。
2> 职能分析:
工厂方法模式:核心工厂类不负责所有产品的创建,而是将具体创建工作交给子类去做。这个核心类仅仅负责给出具体工厂必须实现的接口,而不参与实例化细节。
工厂方法模式可以在不修改工厂角色的情况下引进新产品,该模式中,工厂类与产品类是一一对应,并且是平行等级的结构。
抽象工厂模式:1. 用抽象工厂生产抽象产品
2.用实体工厂生产实体产品
3 .用抽象产品提供实体产品访问接口
4.用实体产品实现自己的功能
3结构模型图
4 总结一下:
工厂设计模式: 1.一个抽象产品类,可以派生出多个具体产品类
2.一个抽象工厂类,可以派生出多个具体工厂类
3.每个具体工厂类,只能创建一个具体产品的实例
抽象设计模式:1.多个抽象产品类,可以派生出多个具体产品类
2.一个抽象工厂类,可以派生出多个具体工厂类
3.每个具体工厂类,可以创建多个具体类的实例
4.工厂模式的思想
工厂模式的思想主要为:多个类似的子类继承同一个父类,对其父类中的变量进行操作;工厂类负责判断、控制哪个子类被执行,而工厂类调用子类完成后,返回的结果是该子类的父类,该父类中的变量已经被操作过了,访问该父类,得到我们想要的结果。
public class Father{ //父类
protected static String one;
protected static String two;
}
public class Son1 extends Father{ //子类
Son1(){
one = "son1"; //操作父类中的变量
}
}
public class Son2 extends Father{ //子类
Son2(){
two = "son2";
}
}
public class Factory { //工厂类
public Father getSon(String s) { //调用子类,返回父类
if (s.equals("1")) {
return new Son1();
} else {
return new Son1();
}
}
}
调用方法举例
Factory factory = new Factory();
Father father = factory.getSon("1"); //调用时不需要判断调用哪个子类,参数不同,工厂自动判断调用的子类。本例调用了Son1子类
System.out.println(father.one); //查看结果
System.out.println(father.two);
工厂类中的创建子类的方法亦可放在父类中实现。
以下分别讲解代理模式、观察者模式、(抽象)工厂模式这三个设计模式的实现
设计模式
设计模式(Design pattern)代表了最佳的实践,通常被有经验的面向对象的软件开发人员所采用。设计模式是软件开发人员在软件开发过程中面临的一般问题的解决方案,这些解决方案是众多软件开发人员经过相当长的一段时间的试验和错误中总结出来的。
序号 | 模式&描述 | 包括 |
1 | 创建性模式(对象怎么来) 这些设计模式提供了一种在创建对象的同时隐藏常见逻辑的方式,而不是使用new运算符直接实例化对象。这使得程序在判断针对某个给定示例需要创建哪些对象时更加灵活。 |
工厂模式(Factory Pattern) 抽象工厂模式(Abstract Factory Pattern) 单例模式(Singleton Pattern) 建造者模式(Builder Pattern) 原型模式(Prototype Pattern) |
2 | 结构型模式(对象和谁有关) 这些设计模式关注类和对象的组合。继承的概念被用来组合接口和定义组合对象获得新功能的方式。 |
适配器模式(Adapter Pattern) 桥接模式(Bridge Pattern) 过滤器模式(Filter、Criteria Pattern) 组合模式(Composite Pattern) 装饰器模式(Decorator Pattern) 外观模式(Facade Pattern) 享元模式(Flyweight Pattern) 代理模式(Proxy Pattern) |
3 | 行为型模式(对象与对象在干什么) 这些设计模式特别关注对象之间的通信。 |
责任链模式(Chain of Responsibility Pattern) 命令模式(Command Pattern) 解释器模式(Interpreter Pattern) 迭代器模式(Iterator Pattern) 中介者模式(Mediator Pattern) 备忘录模式(Memento Pattern) 观察者模式(Observer Pattern) 状态模式(State Pattern) 空对象模式(Null Object Pattern) 策略模式(Strategy Pattern) 模板模式(Template Pattern) 访问者模式(Visitor Pattern) |
4 | J2EE 模式(对象合起来在干什么) 这些设计模式特别关注表示层。这些模式是由 Sun Java Center 鉴定的。 |
MVC 模式(MVC Pattern) 业务代表模式(Business Delegate Pattern) 组合实体模式(Composite Entity Pattern) 数据访问对象模式(Data Access Object Pattern) 前端控制器模式(Front Controller Pattern) 拦截过滤器模式(Intercepting Filter Pattern) 服务定位器模式(Service Locator Pattern) 传输对象模式(Transfer Object Pattern) |
设计模式的六大原则
开闭原则(Open Close Principle)(实现热插拔,提高扩展性)
开闭原则的意思是:对扩展开放,对修改关闭。在程序需要进行拓展的时候,不要去修改原有的代码,实现一个热插拔的效果。简言之,就是为了是程序的扩展性好,易于维护和升级。想要达到这样的效果,我们需要使用接口和抽象类。
里氏代换原则(Liskov Substitution Principle)(实现抽象的规范,实现子父类互相替换)
里氏代换原则是相面对象设计的基本原则之一。里氏代换原则中说,任何基类可以出现的地方,子类一定可以出现。LSP是继承复用的基石,只有当派生类可以替换掉基类,且软件单位的功能不受到影响时,基类才能真正被复用,而派生类也能够在基类的基础上增加新的行为。里氏代换原则是对开闭原则的补充。实现开闭原则的关键步骤就是抽象化,而基类与子类的继承关系就是抽象化的具体实现,所以里氏代换原则是对实现抽象化的具体步骤的规范。
依赖倒转原则(Dependence Inversion Principle)(针对接口编程,实现开闭原则的基础)
此原则是开闭原则的基础,具体内容:针对接口编程,依赖于抽象而不依赖于具体
接口隔离原则(Interface Segregation Principle)
含义:使用多个隔离的接口,比使用单个接口要好。另外一个意思是:降低类之间的耦合度。他强调降低依赖,降低耦合
迪米特原则,又称最少知道原则(Demeter Principle)
含义:一个实体应当尽量少的与其他实体之间发生相互作用,使得系统功能模块相对独立。
最少复用原则(Composite Reuse Principle)
含义:尽量使用合成 / 聚合的方式,而不是使用继承。
工厂模式
属于创建性模式,他提供了一种创建对象的最佳方式。在创建对象时不会对客户端暴露创建逻辑,并且是通过一个共同的接口来指向新创建的对象
介绍:
意图:定义一个创建对象的接口,让其子类自己决定实例化哪一个工厂类,会使其创建过程延迟到子类进行。
主要解决:主要解决接口选择的问题。
如何解决:让其子类实现工厂接口,返回的也是一个抽象的产品。
关键代码:创建过程在其子类执行。
优点:
一个调用者想创建一个对象,只要知道器名称就可以了。
扩展性高,如果想增加一个产品,只要扩展一个工厂类就可以了。
屏蔽产品的具体实现,调用者只关心产品的接口。
缺点:每次增加一个产品时,都需要增加一个具体类和对象实现工厂,使得系统中类的个数成倍增加,在一定程度上增加了系统的复杂度,同时也增加了系统具体类的依赖。这并不是什么好事。
使用场景:
日志记录器:记录可能记录到本地硬盘、系统时间、远程服务器等等。
数据库访问:当用户不知道最后系统采用哪一类数据库,以及数据库可能有变化时。
注意事项:作为一种创建类模式,在任何时候要生成复杂对象的地方,都可以使用工厂方法模式。有一点需要注意的地方就是复杂对象适合使用工厂模式,而简单对象,特别是只需要 new 就可以完成创建的对象,无需使用工厂模式。如果使用工厂模式,就需要你引入一个工厂类,会增加系统的复杂度。
//1. 创建一个接口
interface Person {
// 人类都具有吃的方法
public void eat();
}
//2. 创建实现接口的实体类
class Chinese implements Person {
@Override
public void eat() {
System.out.println("中国人用筷子吃饭");
}
}
class American implements Person {
@Override
public void eat() {
System.out.println("美国人用刀和叉子吃饭");
}
}
class Indian implements Person {
@Override
public void eat() {
System.out.println("印度人用手吃饭");
}
}
//3. 创建一个获取实例的接口
interface Instance {
public Person getInstance(String str);
}
//4. 创建工厂,实现获取实例的接口
class Factory implements Instance {
@Override
public Person getInstance(String str) {
if ("Chinese".equals(str)) {
return new Chinese();
} else if ("American".equals(str)) {
return new American();
} else if ("Indian".equals(str)) {
return new Indian();
} else {
return null;
}
}
}
//5.主方法
public class Factory_Pattern {
public static void main(String[] args) {
//使用该工厂,通过传递消息来获取实体类的对象
Factory factory = new Factory();
Person chinese = factory.getInstance("Chinese");
chinese.eat();
Person american = factory.getInstance("American");
american.eat();
}
}
程序输出:
中国人用筷子吃饭
美国人用刀和叉子吃饭
补充:可以使用枚举类优化,以防止类名输入错误
另补充:
一、一句话概括工厂模式
简单工厂:一个工厂类,一个产品抽象类。
工厂方法:多个工厂类,一个产品抽象类。
抽象工厂:多个工厂类,多个产品抽象类。
二、生活中的工厂模式
简单工厂类:一个麦当劳店,可以生产多种汉堡。
工厂方法类:一个麦当劳店,可以生产多种汉堡。一个肯德基店,也可以生产多种汉堡。
抽象工厂类:百胜餐饮集团下有肯德基和百事公司,肯德基生产汉堡,百事公司生成百事可乐。
抽象工厂模式
是围绕一个超级工厂创建其他工厂的,该超级工厂又称为其他工厂的工厂。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。接口是负责创建一个相关对象的工厂,不需要显示地指定它们的类。每个生成的工厂都能按照工厂模式提供对象。
介绍:
意图:提供一个创建一系列相关或相互依赖对象的接口,而无需指定它们具体的类。
主要解决:接口选择的问题
如何使用:系统的产品有多于一个的产品族,而系统只消费其中某一族的产品。
如何解决:在一个产品族里面,定义多个产品。
关键代码:在一个工厂里聚合多个同类的产品
优点:当一个产品族中的多个对象被设计成一起工作时,它能保证客户端始终只使用相同一个产品族中的对象。
缺点:产品组扩展非常困难,要增加一个系列的某一个产品,既要保证抽象的Creator里加代码,又要在具体的里面加代码。
使用场景:
QQ换皮肤,一整套一起换
生成不同操作系统的程序
注意事项:产品族难扩展,产品等级易扩展。
//1.为形状创建一个接口
interface Shape {
public void draw();
}
// 2. 创建实现形状接口的实体类(3个)
class Rectangle implements Shape {
@Override
public void draw() {
System.out.println("矩形的画画方法");
}
}
class Square implements Shape {
public void draw() {
System.out.println("正方形的画画方法");
}
}
class Circle implements Shape {
@Override
public void draw() {
System.out.println("圆的画画方法");
}
}
//3. 创建一个颜色的接口
interface Color {
public void fill();
}
//4. 创建颜色接口的实现类(3个)
class Red implements Color {
@Override
public void fill() {
System.out.println("红色");
}
}
class Blue implements Color {
@Override
public void fill() {
System.out.println("蓝色");
}
}
class Green implements Color {
@Override
public void fill() {
System.out.println("绿色");
}
}
//5. 为 Shape 和 Color 对象创建 抽象类 来获取工厂
abstract class AbstractFactory {
public abstract Color getColor(String color);
public abstract Shape getShape(String shape);
}
//6. 创建 扩展了 AbstractFactory 的工厂类,基于给定的信息生成实体类的对象
class ShapeFactory extends AbstractFactory {
@Override
public Shape getShape(String shape) {
if (shape.equals("Circle")) {
return new Circle();
} else if (shape.equals("Square")) {
return new Square();
} else if (shape.equals("Rectangle")) {
return new Rectangle();
} else {
return null;
}
}
@Override
public Color getColor(String color) {
return null;
}
}
class ColorFactory extends AbstractFactory {
@Override
public Color getColor(String color) {
if (color.equals("Red")) {
return new Red();
} else if (color.equals("Green")) {
return new Green();
} else if (color.equals("Blue")) {
return new Blue();
} else {
return null;
}
}
@Override
public Shape getShape(String shape) {
return null;
}
}
//7. 创建一个超级工厂(工厂生成器),通过传形状或者颜色信息来获取工厂
class FactoryProducer {
public static AbstractFactory getFactory(String choice) {
if ("Shape".equals(choice)) {
return new ShapeFactory();
} else if ("Color".equals(choice)) {
return new ColorFactory();
} else {
return null;
}
}
}
// 测试类
public class Abstract_Factory_Pattern {
public static void main(String[] args) {
// 获取形状工厂
AbstractFactory shapeFactory = FactoryProducer.getFactory("Shape");
// 获取形状为 Circle 的对象
Shape circle = shapeFactory.getShape("Circle");
circle.draw();
Shape square = shapeFactory.getShape("Square");
square.draw();
// 获取颜色工厂
AbstractFactory colorFactory = FactoryProducer.getFactory("Color");
//获取颜色为 Red 的对象
Color red = colorFactory.getColor("Red");
red.fill();
Color blue = colorFactory.getColor("Blue");
blue.fill();
}
}
输出结果
圆的画画方法
正方形的画画方法
红色
蓝色
代理模式
一个类代表另一个类的功能。这种类型的设计模式属于结构性模式。在代理模式中,我们创建具有现有对象的对象,以便向外界提供功能接口。
介绍
意图:为其他对象提供一种代理以控制对这个对象的访问。
主要解决:在直接访问对象时带来的问题,比如:要访问的对象在远程的服务器上。在面向对象系统中,有些对象由于某些原因(对象创建开销很大、某些操作需要安全控制、需要进程外的访问 等),直接访问会给使用者或者系统带来很多麻烦,我们可以在访问此对象时加上一个对此对象的访问层。
何时使用:想在访问一个类时做一些控制。
如何解决:增加中间层。
关键代码:实现与被代理类组合。
应用实例:
windows里面的快捷方式;
spring aop;
买火车票不一定在火车站买,也可以去代售点买;
一直支票或者银行存单是账户中资金的代理。
优点:
职责清晰
高扩展性
智能化
缺点:
由于在客户端和真实主题之间增加了代理对象,因此有些类型的代理模式可能会造成请求的处理速度变慢
实现代理模式需要额外的工作,有些代理模式的实现非常复杂。
注意事项:
和适配器模式的区别:适配器模式主要改变所考虑对象的接口,而代理模式不能改变所代理类的接口
和装饰器模式的区别:装饰器模式为了增强功能,代理模式是为了加以控制。
//1. 创建一个图片接口
interface Image {
void dispaly();
}
// 2.创建实现接口的实体类
class RealImage implements Image {
private String fileName;
public RealImage() {
}
public RealImage(String fileName) {
this.fileName = fileName;
loadFromDisk(fileName);
}
private void loadFromDisk(String fileName) {
System.out.println("load:" + fileName);
}
@Override
public void dispaly() {
System.out.println("display:" + fileName);
}
}
class ProxyImage implements Image {
private RealImage realImage;
private String fileName;
public ProxyImage() {
}
public ProxyImage(String fileName) {
this.fileName = fileName;
}
@Override
public void dispaly() {
if (realImage == null) {
realImage = new RealImage(fileName);
}
realImage.dispaly();
}
}
// 3.当被请求时,使用ProxyImage 来获取 RealImage类的对象
public class Proxy_Pattern {
public static void main(String[] args) {
Image image = new ProxyImage("test.png");
//第一次调用display需要初始化RealImage
image.dispaly();
System.out.println();
//第二次就不需要了
image.dispaly();
}
}
输出结果
load:test.png
display:test.png
display:test.png
观察者模式
当对象存在一对多关系时,则使用观察者模式。比如,当一个对象被修改,则会自动通知他的依赖。观察者模式输入行为型模式。
介绍:
意图:定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于他的对象都得到通知并被自动更新。
主要解决:一个对象状态改变给其他对象通知的问题,而且要考虑到易用和低耦合,保证高度的协作。
何时使用:一个对象(目标对象)的状态发生改变,所有的依赖对象(观察者对象)都将得到通知,进行广播通知。
如何解决:使用面向对象技术,可以将这种依赖关系弱化。
关键代码:在抽象类里有一个 ArrayList 存放观察者们。
应用实例:拍卖的时候,拍卖师观察最高标价,然后通知其他竞价者竞价。
优点:
观察者和被观察者是抽象耦合的。
建立一套触发机制
缺点:
如果一个被观察者对象有很多的直接和间接的观察者的话,将所有观察者都通知到的话会花费很多时间。
如果观察者和观察目标之间有循环依赖的话,观察目标会触发他们之间进行循环调用,可能导致系统崩溃。
观察者模式没有相应的机制让观察者知道所观察的目标对象是怎么发生变化的,而仅仅只是知道观察目标发生了变化。
注意事项
Java中已经有了对观察者模式的支持类
避免循环调用
如果顺序执行,某一观察者错误会导致系统卡壳,一般采用异步方式。
import java.util.ArrayList;
import java.util.List;
//1.目标对象 绑定所有观察者存放到数组中
public class Subject {
private List observers
= new ArrayList();//观察者数组
private int state; //目标对象的状态
public int getState() { //获取状态
return state;
}
public void setState(int state) {//改变或者设置对象状态时,通知观察者们
this.state = state;
notifyAllObservers();
}
public void attach(Observer observer){//添加需要被通知的观察者
observers.add(observer);
}
public void notifyAllObservers(){ //通知观察者们 目标对象状态已改变
for (Observer observer : observers) {
observer.update();
}
}
}
//2.观察者对象抽象类1
public abstract class Observer {
protected Subject subject; //定义观察者的目标对象
public abstract void update(); //抽象方法
}
//3.创建实体观察者
public class BinaryObserver extends Observer{
public BinaryObserver(Subject subject){
this.subject = subject;
this.subject.attach(this);
}
@Override
public void update() { //实现抽象方法
System.out.println( "Binary String: "
+ Integer.toBinaryString( subject.getState() ) );
}
}
//2.观察者对象抽象类2
public class OctalObserver extends Observer{
public OctalObserver(Subject subject){
this.subject = subject;
this.subject.attach(this);//添加当前对象到目标对象中
}
@Override
public void update() {
System.out.println( "Octal String: "
+ Integer.toOctalString( subject.getState() ) );
}
}
//2.观察者对象抽象类3
public class HexaObserver extends Observer{
public HexaObserver(Subject subject){
this.subject = subject;
this.subject.attach(this);
}
@Override
public void update() {
System.out.println( "Hex String: "
+ Integer.toHexString( subject.getState() ).toUpperCase() );
}
}
//执行效果 使用 Subject 和实体观察者对象。
public class ObserverPatternDemo {
public static void main(String[] args) {
Subject subject = new Subject();//创建一个目标对象
new HexaObserver(subject);//初始化观察者对象1
new OctalObserver(subject);//初始化观察者对象2
new BinaryObserver(subject);//初始化观察者对象3 并传入目标对象
System.out.println("First state change: 15");
subject.setState(15);//第一次设置初始的目标对象的状态,会通知到观察者们,观察者们会反馈
System.out.println("Second state change: 10");
subject.setState(10);//第二次执行状态改变,再次通知观察者们并反馈信息
}
}
执行结果打印
First state change: 15
Hex String: F
Octal String: 17
Binary String: 1111
Second state change: 10
Hex String: A
Octal String: 12
Binary String: 1010