面试——常用的设计模式

转载连接: https://blog.csdn.net/qq_24309787/article/details/82257772?utm_medium=distribute.pc_relevant.none-task-blog-OPENSEARCH-17.nonecase&depth_1-utm_source=distribute.pc_relevant.none-task-blog-OPENSEARCH-17.nonecase

 

一、软件设计模式的几种分类:

1.1.  创建型

创建对象时,不再由我们直接实例化对象;而是根据特定场景,由程序来确定创建对象的方式,从而保证更大的性能、更好的架构优势。创建型模式主要有简单工厂模式(并不是23种设计模式之一)、工厂方法、抽象工厂模式单例模式、生成器模式和原型模式。

1.2.  结构型

用于帮助将多个对象组织成更大的结构。结构型模式主要有适配器模式adapter、桥接模式bridge、组合器模式component、装饰器模式decorator、门面模式、亨元模式flyweight和代理模式proxy

1.3.  行为型

用于帮助系统间各对象的通信,以及如何控制复杂系统中流程。行为型模式主要有命令模式command、解释器模式、迭代器模式、中介者模式、备忘录模式、观察者模式、状态模式state、策略模式、模板模式和访问者模式。

二、常用的设计模式介绍

2.1.  单例模式(singleton)

      有些时候,允许自由创建某个类的实例没有意义,还可能造成系统性能下降。如果一个类始终只能创建一个实例,则这个类被称为单例类,这种模式就被称为单例模式。

    一般建议单例模式的方法命名为:getInstance(),这个方法的返回类型肯定是单例类的类型了。getInstance方法可以有参数,这些参数可能是创建类实例所需要的参数,当然,大多数情况下是不需要的。

 
  1. public class Singleton {

  2.  
  3. private static Singleton singleton;

  4.  
  5. private Singleton() {

  6. }

  7.  
  8. public static Singleton getInstance() {

  9. if (singleton == null) {

  10. singleton = new Singleton();

  11. }

  12. return singleton;

  13. }

  14. }

优点: 
    1.在单例模式中,活动的单例只有一个实例,对单例类的所有实例化得到的都是相同的一个实例。这样就 防止其它对象对自己的实例化,确保所有的对象都访问一个实例 
    2.单例模式具有一定的伸缩性,类自己来控制实例化进程,类就在改变实例化进程上有相应的伸缩性。 
    3.提供了对唯一实例的受控访问。 
    4.由于在系统内存中只存在一个对象,因此可以 节约系统资源,当 需要频繁创建和销毁的对象时单例模式无疑可以提高系统的性能。 
    5.允许可变数目的实例。 
    6.避免对共享资源的多重占用。 
缺点: 
    1.不适用于变化的对象,如果同一类型的对象总是要在不同的用例场景发生变化,单例就会引起数据的错误,不能保存彼此的状态。 
    2.由于单利模式中没有抽象层,因此单例类的扩展有很大的困难。 
    3.单例类的职责过重,在一定程度上违背了“单一职责原则”。 
    4.滥用单例将带来一些负面问题,如为了节省资源将数据库连接池对象设计为的单例类,可能会导致共享连接池对象的程序过多而出现连接池溢出;如果实例化的对象长时间不被利用,系统会认为是垃圾而被回收,这将导致对象状态的丢失。 
使用注意事项: 
    1.使用时不能用反射模式创建单例,否则会实例化一个新的对象 
    2.使用懒单例模式时注意线程安全问题 
    3.单例模式和懒单例模式构造方法都是私有的,因而是不能被继承的,有些单例模式可以被继承(如登记式模式) 
适用场景: 
    单例模式只允许创建一个对象,因此节省内存,加快对象访问速度,因此对象需要被公用的场合适合使用,如多个模块使用同一个数据源连接对象等等。如: 
    1.需要频繁实例化然后销毁的对象。 
    2.创建对象时耗时过多或者耗资源过多,但又经常用到的对象。 
    3.有状态的工具类对象。 
    4.频繁访问数据库或文件的对象。 
以下都是单例模式的经典使用场景: 
    1.资源共享的情况下,避免由于资源操作时导致的性能或损耗等。如上述中的日志文件,应用配置。 
    2.控制资源的情况下,方便资源之间的互相通信。如线程池等。 
应用场景举例: 
    1.外部资源:每台计算机有若干个打印机,但只能有一个PrinterSpooler,以避免两个打印作业同时输出到打印机。内部资源:大多数软件都有一个(或多个)属性文件存放系统配置,这样的系统应该有一个对象管理这些属性文件 
    2. Windows的TaskManager(任务管理器)就是很典型的单例模式(这个很熟悉吧),想想看,是不是呢,你能打开两个windows task manager吗? 不信你自己试试看哦~ 
    3. windows的Recycle Bin(回收站)也是典型的单例应用。在整个系统运行过程中,回收站一直维护着仅有的一个实例。 
    4. 网站的计数器,一般也是采用单例模式实现,否则难以同步。 
    5. 应用程序的日志应用,一般都何用单例模式实现,这一般是由于共享的日志文件一直处于打开状态,因为只能有一个实例去操作,否则内容不好追加。 
    6. Web应用的配置对象的读取,一般也应用单例模式,这个是由于配置文件是共享的资源。 
    7. 数据库连接池的设计一般也是采用单例模式,因为数据库连接是一种数据库资源。数据库软件系统中使用数据库连接池,主要是节省打开或者关闭数据库连接所引起的效率损耗,这种效率上的损耗还是非常昂贵的,因为何用单例模式来维护,就可以大大降低这种损耗。 
    8. 多线程的线程池的设计一般也是采用单例模式,这是由于线程池要方便对池中的线程进行控制。 
    9. 操作系统的文件系统,也是大的单例模式实现的具体例子,一个操作系统只能有一个文件系统。 
   10. HttpApplication 也是单位例的典型应用。熟悉ASP.Net(IIS)的整个请求生命周期的人应该知道HttpApplication也是单例模式,所有的HttpModule都共享一个HttpApplication实例. 

2.2.  观察者模式(Observer)

     对象间一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新。Android中的各种Listener就使用到了这一设计模式,只要用户对手机进行操作,对应的listener就会被通知,并作出响应的处理。 
观察者模式UML图 
看不懂图的人端着小板凳到这里来,给你举个栗子��:假设有三个人,小美(女,28),老王和老李。小美很漂亮,很风骚,老王和老李是两个中年男屌丝,时刻关注着小美的一举一动。有一天,小美说了一句:我老公今天不在家,一个人好无聊啊~~~,这句话被老王和老李听到了,结果乐坏了,蹭蹭蹭,没一会儿,老王就冲到小美家门口了,于是进门了………
在这里,小美是被观察者,老王和老李是观察者,被观察者发出一条信息,然后被观察者进行相应的处理,看代码:

 
  1. public interface Person {

  2. //老王和老李通过这个接口可以接收到小美发过来的消息

  3. void getMessage(String s);

  4. }

这个接口相当于老王和老李的电话号码,小美发送通知的时候就会拨打getMessage这个电话,拨打电话就是调用接口,看不懂没关系,先往下看

 
  1. public class LaoWang implements Person {

  2.  
  3. private String name = "老王";

  4.  
  5. public LaoWang() {

  6. }

  7.  
  8. @Override

  9. public void getMessage(String s) {

  10. System.out.println(name + "接到了小美打过来的电话,电话内容是:" + s);

  11. }

  12.  
  13. }

  14.  
  15. public class LaoLi implements Person {

  16.  
  17. private String name = "老李";

  18.  
  19. public LaoLi() {

  20. }

  21.  
  22. @Override

  23. public void getMessage(String s) {

  24. System.out.println(name + "接到了小美打过来的电话,电话内容是:->" + s);

  25. }

  26.  
  27. }

代码很简单,我们再看看小美的代码:

 
  1. public class XiaoMei {

  2. List<Person> list = new ArrayList<Person>();

  3. public XiaoMei(){

  4. }

  5.  
  6. public void addPerson(Person person){

  7. list.add(person);

  8. }

  9.  
  10. //遍历list,把自己的通知发送给所有暗恋自己的人

  11. public void notifyPerson() {

  12. for(Person person:list){

  13. person.getMessage("今天家里就我一个人,你们过来吧,谁先过来谁就能得到我!");

  14. }

  15. }

  16. }

我们写一个测试类来看一下结果对不对

 
  1. public class Test {

  2. public static void main(String[] args) {

  3.  
  4. XiaoMei xiao_mei = new XiaoMei();

  5. LaoWang lao_wang = new LaoWang();

  6. LaoLi lao_li = new LaoLi();

  7.  
  8. //老王和老李在小美那里都注册了一下

  9. xiao_mei.addPerson(lao_wang);

  10. xiao_mei.addPerson(lao_li);

  11.  
  12. //小美向老王和老李发送通知

  13. xiao_mei.notifyPerson();

  14. }

  15. }

运行结果我截图了 
运行结果 
完美~~~

2.3.  装饰者模式 (Decorator Pattern)

对已有的业务逻辑进一步的封装,使其增加额外的功能,如java中的IO流就使用了装饰者模式,用户在使用的时候,可以任意组装,达到自己想要的效果。 
举个栗子,我想吃三明治,首先我需要一根大大的香肠,我喜欢吃奶油,在香肠上面加一点奶油,再放一点蔬菜,最后再用两片面包加一下,很丰盛的一顿午饭,营养又健康,那我们应该怎么来写代码呢? 
首先,我们需要写一个Food类,让其他所有食物都来继承这个类,看代码:

 
  1. public class Food {

  2.  
  3. private String food_name;

  4.  
  5. public Food() {

  6. }

  7.  
  8. public Food(String food_name) {

  9. this.food_name = food_name;

  10. }

  11.  
  12. public String make() {

  13. return food_name;

  14. };

  15. }

代码很简单,我就不解释了,然后我们写几个子类继承它:

 
  1. //面包类

  2. public class Bread extends Food {

  3.  
  4. private Food basic_food;

  5.  
  6. public Bread(Food basic_food) {

  7. this.basic_food = basic_food;

  8. }

  9.  
  10. public String make() {

  11. return basic_food.make()+"+面包";

  12. }

  13. }

  14.  
  15. //奶油类

  16. public class Cream extends Food {

  17.  
  18. private Food basic_food;

  19.  
  20. public Cream(Food basic_food) {

  21. this.basic_food = basic_food;

  22. }

  23.  
  24. public String make() {

  25. return basic_food.make()+"+奶油";

  26. }

  27. }

  28.  
  29. //蔬菜类

  30. public class Vegetable extends Food {

  31.  
  32. private Food basic_food;

  33.  
  34. public Vegetable(Food basic_food) {

  35. this.basic_food = basic_food;

  36. }

  37.  
  38. public String make() {

  39. return basic_food.make()+"+蔬菜";

  40. }

  41.  
  42. }

这几个类都是差不多的,构造方法传入一个Food类型的参数,然后在make方法中加入一些自己的逻辑,如果你还是看不懂为什么这么写,不急,你看看我的Test类是怎么写的,一看你就明白了

 
  1. public class Test {

  2. public static void main(String[] args) {

  3. Food food = new Bread(new Vegetable(new Cream(new Food("香肠"))));

  4. System.out.println(food.make());

  5. }

  6. }

看到没有,一层一层封装,我没从里往外看:最里面我new了一个香肠,在香肠的外面我包裹了一层奶油,在奶油的外面我又加了一层蔬菜,最外面我放的是面包,是不是很形象,哈哈 ~ 这个设计模式简直跟现实生活中一摸一样,看懂了吗? 
我们看看运行结果吧 
运行结果 
一个三明治就做好了~~~

应用实例:

       1、孙悟空有 72 变,当他变成"庙宇"后,他的根本还是一只猴子,但是他又有了庙宇的功能。

       2、不论一幅画有没有画框都可以挂在墙上,但是通常都是有画框的,并且实际上是画框被挂在墙上。在挂在墙上之前,画可以被蒙上玻璃,装到框子里;这时画、玻璃和画框形成了一个物体。

优点:装饰类和被装饰类可以独立发展,不会相互耦合,装饰模式是继承的一个替代模式,装饰模式可以动态扩展一个实现类的功能。

缺点:多层装饰比较复杂。

适用环境:

(1)在不影响其他对象的情况下,以动态、透明的方式给单个对象添加职责。

(2)处理那些可以撤消的职责。

(3)当不能采用生成子类的方法进行扩充时。一种情况是,可能有大量独立的扩展,为支持每一种组合将产生大量的 子类,使得子类数目呈爆炸性增长。另一种情况可能是因为类定义被隐藏,或类定义不能用于生成子类。

2.4. 适配器模式 

适配器模式(Adapter Pattern)是作为两个不兼容的接口之间的桥梁。这种类型的设计模式属于结构型模式,它结合了两个独立接口的功能。

这种模式涉及到一个单一的类,该类负责加入独立的或不兼容的接口功能。举个真实的例子,读卡器是作为内存卡和笔记本之间的适配器。您将内存卡插入读卡器,再将读卡器插入笔记本,这样就可以通过笔记本来读取内存卡。

假设一个手机充电器需要的电压是20V,但是正常的电压是220V,这时候就需要一个变压器,将220V的电压转换成20V的电压,这样,变压器就将20V的电压和手机联系起来了。

 
  1. public class Test {

  2. public static void main(String[] args) {

  3. Phone phone = new Phone();

  4. VoltageAdapter adapter = new VoltageAdapter();

  5. phone.setAdapter(adapter);

  6. phone.charge();

  7. }

  8. }

  9.  
  10. // 手机类

  11. class Phone {

  12.  
  13. public static final int V = 220;// 正常电压220v,是一个常量

  14.  
  15. private VoltageAdapter adapter;

  16.  
  17. // 充电

  18. public void charge() {

  19. adapter.changeVoltage();

  20. }

  21.  
  22. public void setAdapter(VoltageAdapter adapter) {

  23. this.adapter = adapter;

  24. }

  25. }

  26.  
  27. // 变压器

  28. class VoltageAdapter {

  29. // 改变电压的功能

  30. public void changeVoltage() {

  31. System.out.println("正在充电...");

  32. System.out.println("原始电压:" + Phone.V + "V");

  33. System.out.println("经过变压器转换之后的电压:" + (Phone.V - 200) + "V");

  34. }

  35. }

适配器模式优点: 1、可以让任何两个没有关联的类一起运行。 2、提高了类的复用。 3、增加了类的透明度。 4、灵活性好。

缺点: 1、过多地使用适配器,会让系统非常零乱,不易整体进行把握。比如,明明看到调用的是 A 接口,其实内部被适配成了 B 接口的实现,一个系统如果太多出现这种情况,无异于一场灾难。因此如果不是很有必要,可以不使用适配器,而是直接对系统进行重构。 2.由于 JAVA 至多继承一个类,所以至多只能适配一个适配者类,而且目标类必须是抽象类。

使用场景:有动机地修改一个正常运行的系统的接口,这时应该考虑使用适配器模式。

注意事项:适配器不是在详细设计时添加的,而是解决正在服役的项目的问题。

2.5. 工厂模式 (Factory Pattern)

就是建立一个工厂类,对实现了同一接口的一些类进行实例的创建。简单工厂模式的实质是由一个工厂类根据传入的参数,动态决定应该创建哪一个产品类(这些产品类继承自一个父类或接口)的实例。

实现方式:

a)     抽象产品类(也可以是接口)

b)     多个具体的产品类

c)     工厂类(包括创建a的实例的方法)

简单工厂模式:一个抽象的接口,多个抽象接口的实现类,一个工厂类,用来实例化抽象的接口

 
  1. // 抽象产品类

  2. abstract class Car {

  3. public void run();

  4.  
  5. public void stop();

  6. }

  7.  
  8. // 具体实现类

  9. class Benz implements Car {

  10. public void run() {

  11. System.out.println("Benz开始启动了。。。。。");

  12. }

  13.  
  14. public void stop() {

  15. System.out.println("Benz停车了。。。。。");

  16. }

  17. }

  18.  
  19. class Ford implements Car {

  20. public void run() {

  21. System.out.println("Ford开始启动了。。。");

  22. }

  23.  
  24. public void stop() {

  25. System.out.println("Ford停车了。。。。");

  26. }

  27. }

  28.  
  29. // 工厂类

  30. class Factory {

  31. public static Car getCarInstance(String type) {

  32. Car c = null;

  33. if ("Benz".equals(type)) {

  34. c = new Benz();

  35. }

  36. if ("Ford".equals(type)) {

  37. c = new Ford();

  38. }

  39. return c;

  40. }

  41. }

  42.  
  43. public class Test {

  44.  
  45. public static void main(String[] args) {

  46. Car c = Factory.getCarInstance("Benz");

  47. if (c != null) {

  48. c.run();

  49. c.stop();

  50. } else {

  51. System.out.println("造不了这种汽车。。。");

  52. }

  53.  
  54. }

  55.  
  56. }

工厂方法模式:有四个角色,抽象工厂模式,具体工厂模式,抽象产品模式,具体产品模式。不再是由一个工厂类去实例化具体的产品,而是由抽象工厂的子类去实例化产品

 
  1. // 抽象产品角色

  2. public interface Moveable {

  3. void run();

  4. }

  5.  
  6. // 具体产品角色

  7. public class Plane implements Moveable {

  8. @Override

  9. public void run() {

  10. System.out.println("plane....");

  11. }

  12. }

  13.  
  14. public class Broom implements Moveable {

  15. @Override

  16. public void run() {

  17. System.out.println("broom.....");

  18. }

  19. }

  20.  
  21. // 抽象工厂

  22. public abstract class VehicleFactory {

  23. abstract Moveable create();

  24. }

  25.  
  26. // 具体工厂

  27. public class PlaneFactory extends VehicleFactory {

  28. public Moveable create() {

  29. return new Plane();

  30. }

  31. }

  32.  
  33. public class BroomFactory extends VehicleFactory {

  34. public Moveable create() {

  35. return new Broom();

  36. }

  37. }

  38.  
  39. // 测试类

  40. public class Test {

  41. public static void main(String[] args) {

  42. VehicleFactory factory = new BroomFactory();

  43. Moveable m = factory.create();

  44. m.run();

  45. }

  46. }

 

优点:

工厂类是整个模式的关键.包含了必要的逻辑判断,根据外界给定的信息,决定究竟应该创建哪个具体类的对象.通过使用工厂类,外界可以从直接创建具体产品对象的尴尬局面摆脱出来,仅仅需要负责“消费”对象就可以了。而不必管这些对象究竟如何创建及如何组织的.明确了各自的职责和权利,有利于整个软件体系结构的优化。

缺点:

由于工厂类集中了所有实例的创建逻辑,违反了高内聚责任分配原则,将全部创建逻辑集中到了一个工厂类中;它所能创建的类只能是事先考虑到的,如果需要添加新的类,则就需要改变工厂类了。当系统中的具体产品类不断增多时候,可能会出现要求工厂类根据不同条件创建不同实例的需求.这种对条件的判断和对具体产品类型的判断交错在一起,很难避免模块功能的蔓延,对系统的维护和扩展非常不利;

2.6. 抽象工厂模式 (Abstract Factory Pattern)

与工厂方法模式不同的是,工厂方法模式中的工厂只生产单一的产品,而抽象工厂模式中的工厂生产多个产品

 
  1. /抽象工厂类

  2. public abstract class AbstractFactory {

  3. public abstract Vehicle createVehicle();

  4. public abstract Weapon createWeapon();

  5. public abstract Food createFood();

  6. }

  7. //具体工厂类,其中Food,Vehicle,Weapon是抽象类,

  8. public class DefaultFactory extends AbstractFactory{

  9. @Override

  10. public Food createFood() {

  11. return new Apple();

  12. }

  13. @Override

  14. public Vehicle createVehicle() {

  15. return new Car();

  16. }

  17. @Override

  18. public Weapon createWeapon() {

  19. return new AK47();

  20. }

  21. }

  22. //测试类

  23. public class Test {

  24. public static void main(String[] args) {

  25. AbstractFactory f = new DefaultFactory();

  26. Vehicle v = f.createVehicle();

  27. v.run();

  28. Weapon w = f.createWeapon();

  29. w.shoot();

  30. Food a = f.createFood();

  31. a.printName();

  32. }

  33. }

 

持续更新中。。。。。。

 

 

参考链接:

几种常用的设计模式介绍

面试必备:常用的设计模式总结

十种常用的设计模式(大部分自己总结,部分摘抄)

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值