资讯专栏INFORMATION COLUMN

通熟易懂的设计模式(二)

daydream / 2116人阅读

摘要:提供酒店相关的接口返回该时间段有效的酒店提供航班相关的接口返回该时间段有效的航班提供一个旅行对外的接口,一次返回酒店和航班信息调用旅行外观模式享元模式享元模式主要用于减少创建对象的数量,以减少内存占用和提高性能。

组合模式(Composite pattern)

组合模式看起来就像对象组的树形结构,一个对象里面包含一个或一组其他的对象。它是属于结构型模式。
例如,一个公司包括很多个部门,每个部门又包括很多人,这个用数据结构来表示就是树形结构,实际上也是用到来组合模式,多个人组成一个部门,多个部门组成一个公司。

例如,我们用下面这个公司、部门、员工的例子来更好的理解组合模式。

</>复制代码

  1. class Company {
  2. private String name;
  3. private List depts;
  4. }
  5. class Dept {
  6. private String name;
  7. private List users;
  8. }
  9. class User {
  10. private String name;
  11. }
装饰模式(Decorator pattern)

装饰器设计模式允许我们动态地向对象添加功能和行为,而不会影响同一类中其他现有对象的行为。并且可以根据我们的要求和选择将此自定义功能应用于单个对象。
假如使用继承来扩展类的行为,这发生在编译期,该类的所有实例都获得扩展行为。

装饰器设计模式的特点:
它允许我们在运行时向对象(而不是类)添加功能。
它是一种结构模式,它为现有类提供了一个包装器。
它使用抽象类或接口与组合来实现包装器。
它创建装饰器类,它包装原始类并通过保持类方法的签名不变来提供其他功能。
它最常用于应用单一责任原则,因为我们将功能划分为具有独特关注区域的类。

例如,我们用下面这个画图形的例子来更好的理解装饰模式。

</>复制代码

  1. //定义一个形状的接口
  2. public interface Shape {
  3. void draw();
  4. void resize();
  5. }
  6. //一个画圆的实现
  7. public class Circle implements Shape {
  8. @Override
  9. public void draw() {
  10. System.out.println("Drawing Circle");
  11. }
  12. @Override
  13. public void resize() {
  14. System.out.println("Resizing Circle");
  15. }
  16. }
  17. //一个画矩形的实现
  18. public class Rectangle implements Shape {
  19. @Override
  20. public void draw() {
  21. System.out.println("Drawing Rectangle");
  22. }
  23. @Override
  24. public void resize() {
  25. System.out.println("Resizing Rectangle");
  26. }
  27. }
  28. //定义一个形状的装饰器抽象类,并用组合模式定义一个形状的属性
  29. public abstract class ShapeDecorator implements Shape {
  30. protected Shape decoratedShape;
  31. public ShapeDecorator(Shape decoratedShape) {
  32. super();
  33. this.decoratedShape = decoratedShape;
  34. }
  35. }
  36. //颜色的枚举
  37. public enum Color {
  38. RED,
  39. GREEN,
  40. BLUE
  41. }
  42. //线条样式的枚举
  43. public enum LineStyle {
  44. SOLID,
  45. DASH,
  46. DOT
  47. }
  48. //定义一个填充颜色的实现类实现装饰器,并重写 draw() 方法,resize() 方法我们可以保持不变,也可以自定义,看使用场景
  49. public class FillColorDecorator extends ShapeDecorator {
  50. protected Color color;
  51. public FillColorDecorator(Shape decoratedShape, Color color) {
  52. super(decoratedShape);
  53. this.color = color;
  54. }
  55. @Override
  56. public void draw() {
  57. decoratedShape.draw();
  58. System.out.println("Fill Color: " + color);
  59. }
  60. @Override
  61. public void resize() {
  62. decoratedShape.resize();
  63. }
  64. }
  65. //定义一个线条样式的实现类实现装饰器,并重写 draw() 方法,resize() 方法我们可以保持不变,也可以自定义,看使用场景
  66. public class LineStyleDecorator extends ShapeDecorator {
  67. protected LineStyle style;
  68. public LineStyleDecorator(Shape decoratedShape, LineStyle style) {
  69. super(decoratedShape);
  70. this.style = style;
  71. }
  72. @Override
  73. public void draw() {
  74. decoratedShape.draw();
  75. System.out.println("Line Style: " + style);
  76. }
  77. //
  78. @Override
  79. public void resize() {
  80. decoratedShape.resize();
  81. }
  82. }
  83. //使用装饰器模式
  84. public class Client {
  85. public static void main(String[] args) {
  86. //在使用时可以任意组装,提升代码灵活性和扩展性。
  87. Shape circle = new FillColorDecorator(new LineStyleDecorator(new Circle(), LineStyle.DASH), Color.RED);
  88. circle.draw();
  89. }
  90. }
外观模式(Facade Pattern)

它提供了一个可以访问系统的接口,这个接口里面的实现可能很复杂,调用了其他多个接口,我们并不知道它里面的具体实现,隐藏了系统的复杂性。它属于结构型模式。

它的优点:
1、减少系统相互依赖。
2、提高灵活性。
3、提高了安全性。

它的缺点:
不符合开闭原则,如果要改东西很麻烦,继承重写都不合适。

以旅行社网站为例,它可以预订酒店和航班,我们来更好的理解下这个模式。

</>复制代码

  1. //提供酒店相关的接口
  2. public class HotelBooker{
  3. public ArrayList getHotelNamesFor(Date from, Date to) {
  4. //返回该时间段有效的酒店
  5. }
  6. }
  7. //提供航班相关的接口
  8. public class FlightBooker{
  9. public ArrayList getFlightsFor(Date from, Date to) {
  10. //返回该时间段有效的航班
  11. }
  12. }
  13. //提供一个旅行对外的接口,一次返回酒店和航班信息
  14. public class TravelFacade{
  15. private HotelBooker hotelBooker;
  16. private FlightBooker flightBooker;
  17. public void getFlightsAndHotels(Date from, Data to) { ArrayList flights = flightBooker.getFlightsFor(from, to); ArrayList hotels = hotelBooker.getHotelsFor(from, to);
  18. }
  19. }
  20. //调用旅行外观模式
  21. public class Client{
  22. public static void main(String[] args) {
  23. TravelFacade facade = new TravelFacade();
  24. facade.getFlightsAndHotels(from, to);
  25. }
  26. }
享元模式(Flyweight Pattern)

享元模式主要用于减少创建对象的数量,以减少内存占用和提高性能。使许多细粒度对象的重用,使得大量对象的利用更加有效。它属于结构型模式。

它的优点:
大大减少对象的创建,降低系统的内存,使效率提高。

它的缺点:
因为一个对象大家共享,最好就不要有状态区分,假如有状态的话,
需要分离出外部状态和内部状态,而且外部状态具有固有化的性质,不应该随着内部状态的变化而变化,否则会造成系统的混乱。

例如,我们用一个画线条的例子来更好的理解这个模式。

</>复制代码

  1. //定义一个画线条的接口
  2. public interface LineFlyweight{
  3. public Color getColor();
  4. public void draw(Point location);
  5. }
  6. //定义一个画线条的实现
  7. public class Line implements LineFlyweight{
  8. private Color color;
  9. public Line(Color c){
  10. color = c;
  11. }
  12. public Color getColor(){
  13. return color;
  14. }
  15. public void draw(Point location){
  16. //实现
  17. }
  18. }
  19. //定一个画线条的工厂类,根据颜色来获取线条,并且把不同颜色的线存储在一个 pool 中,方便下次直接使用
  20. public class LineFlyweightFactory{
  21. private List pool;
  22. public LineFlyweightFactory(){
  23. pool = new ArrayList();
  24. }
  25. public LineFlyweight getLine(Color c){
  26. //循环检查这个颜色的线是否存在,存在直接返回
  27. for(LineFlyweight line: pool){
  28. if(line.getColor().equals(c)){
  29. return line;
  30. }
  31. }
  32. //假如不存在,就创建一个放入这个 pool 中,方便下次直接使用
  33. LineFlyweight line = new Line(c);
  34. pool.add(line);
  35. return line;
  36. }
  37. }
  38. //调用享元模式
  39. public class Client{
  40. public static void main(String[] args) {
  41. //调用
  42. LineFlyweightFactory factory = new LineFlyweightFactory();
  43. LineFlyweight line = factory.getLine(Color.RED);
  44. LineFlyweight line2 = factory.getLine(Color.RED);
  45. line.draw(new Point(100, 100));
  46. line2.draw(new Point(200, 100));
  47. }
  48. }
代理模式(Proxy Pattern)

它通过一个代理类对外提供访问,代理类在真正调用实现类。对外部来说,并不知道真正实现类的详情,提高类系统的安全性。我们可以在代理类里做一系列拦截,把异常的请求都提前处理掉,扩展性很高。它属于结构型模式。

它的优点:
1、职责清晰。
2、高扩展性。
3、更安全。

它的缺点:
1、由于在客户端和真实主题之间增加了代理对象,因此可能会造成请求的处理速度变慢。
2、实现代理模式需要额外的工作,有些代理模式的实现非常复杂。

例如,我们用下面这个代理显示图片的例子来更好的理解代理模式。

</>复制代码

  1. //定义的图片接口
  2. public interface Image{
  3. public void displayImage();
  4. }
  5. //真正的实现类
  6. public class RealImage implements Image{
  7. public RealImage(URL url) {
  8. //加载这个图片
  9. loadImage(url);
  10. }
  11. public void displayImage() {
  12. //显示这个图片
  13. }
  14. private void loadImage(URL url) {
  15. }
  16. }
  17. //代理类
  18. public class ProxyImage implements Image{
  19. private URL url;
  20. public ProxyImage(URL url) {
  21. this.url = url;
  22. }
  23. //this method delegates to the real image
  24. public void displayImage() {
  25. RealImage real = new RealImage(url);
  26. real.displayImage();
  27. }
  28. }
  29. //使用代理模式
  30. public class Client {
  31. public static void main(String[] args) {
  32. Image image = new ProxyImage("test.png");
  33. image.display();
  34. }
  35. }

代理模式分为静态代理和动态代理,静态代理的真正实现类是提前写好并且编译好的,动态代理的真正实现类是运行是生成并且编译的,上面的例子使用的是静态代理。
动态代理又分为 JDK动态代理 和 CGLIB动态代理,JDK动态代理是基于接口的动态代理,CGLIB动态代理是基于类的代理。有兴趣的可以找下相关资料。

责任链模式(Chain of Responsibility Pattern)

责任链模式用于管理对象之间的算法,关系和责任,通过将多个不同处理对象链接在一起处理请求,降低耦合度,提高系统灵活性。它属于行为型模式。

它的优点:
1、降低耦合度。
2、简化了对象。
3、增强给对象指派职责的灵活性。
4、增加新的请求处理类很方便。

它的缺点:
1、系统性能将受到一定影响,而且在进行代码调试时不太方便,可能会造成循环调用。
2、可能不容易观察运行时的特征,不方便排错。

例如,我们用下面这个记录日志的例子来更好的理解责任链模式。

</>复制代码

  1. //定义一个抽象的日志接口,并且提供一个可以设置下一个处理日志对象的方法
  2. public abstract class AbstractLogger {
  3. public static int INFO = 1;
  4. public static int DEBUG = 2;
  5. public static int ERROR = 3;
  6. protected int level;
  7. //责任链中的下一个元素
  8. protected AbstractLogger nextLogger;
  9. public void setNextLogger(AbstractLogger nextLogger){
  10. this.nextLogger = nextLogger;
  11. }
  12. public void logMessage(int level, String message){
  13. if(this.level <= level){
  14. write(message);
  15. }
  16. if(nextLogger !=null){
  17. nextLogger.logMessage(level, message);
  18. }
  19. }
  20. //抽象方法
  21. abstract protected void write(String message);
  22. }
  23. //定义一个标准日志的实现类
  24. public class ConsoleLogger extends AbstractLogger {
  25. public ConsoleLogger(int level){
  26. this.level = level;
  27. }
  28. @Override
  29. protected void write(String message) {
  30. System.out.println("Standard Console::Logger: " + message);
  31. }
  32. }
  33. //定义一个错误日志的实现类
  34. public class ErrorLogger extends AbstractLogger {
  35. public ErrorLogger(int level){
  36. this.level = level;
  37. }
  38. @Override
  39. protected void write(String message) {
  40. System.out.println("Error Console::Logger: " + message);
  41. }
  42. }
  43. //定义一个文件日志的实现类
  44. public class FileLogger extends AbstractLogger {
  45. public FileLogger(int level){
  46. this.level = level;
  47. }
  48. @Override
  49. protected void write(String message) {
  50. System.out.println("File::Logger: " + message);
  51. }
  52. }
  53. //使用责任链模式
  54. public class Client {
  55. //设置责任链的调用顺序
  56. private static AbstractLogger getChainOfLoggers(){
  57. AbstractLogger errorLogger = new ErrorLogger(AbstractLogger.ERROR);
  58. AbstractLogger fileLogger = new FileLogger(AbstractLogger.DEBUG);
  59. AbstractLogger consoleLogger = new ConsoleLogger(AbstractLogger.INFO);
  60. errorLogger.setNextLogger(fileLogger);
  61. fileLogger.setNextLogger(consoleLogger);
  62. return errorLogger;
  63. }
  64. public static void main(String[] args) {
  65. AbstractLogger loggerChain = getChainOfLoggers();
  66. loggerChain.logMessage(AbstractLogger.INFO, "info level information.");
  67. loggerChain.logMessage(AbstractLogger.DEBUG,
  68. "debug level information.");
  69. loggerChain.logMessage(AbstractLogger.ERROR,
  70. "error information.");
  71. }
  72. }
命令模式(Command Pattern)

命令模式是请求以命令的形式包裹在对象中,并传给调用对象。调用对象寻找可以处理该命令的合适的对象,并把该命令传给相应的对象,该对象执行命令。它属于行为型模式。

它的优点:
1、降低了系统耦合度。
2、新的命令可以很容易添加到系统中去。

它的缺点:
使用命令模式可能会导致某些系统有过多的具体命令类。

例如,我们用下面这个开关灯的例子来更好的理解命令模式。

</>复制代码

  1. // 定义执行命令接口
  2. public interface Command{
  3. public void execute();
  4. }
  5. //开灯命令实现类
  6. public class LightOnCommand implements Command{
  7. Light light;
  8. public LightOnCommand(Light light){
  9. this.light = light;
  10. }
  11. public void execute(){
  12. light.switchOn();
  13. }
  14. }
  15. //关灯命令实现类
  16. public class LightOffCommand implements Command{
  17. Light light;
  18. public LightOffCommand(Light light){
  19. this.light = light;
  20. }
  21. public void execute(){
  22. light.switchOff();
  23. }
  24. }
  25. //真正开关灯的类
  26. public class Light{
  27. private boolean on;
  28. public void switchOn(){
  29. on = true;
  30. }
  31. public void switchOff(){
  32. on = false;
  33. }
  34. }
  35. //根据不同命令执行开关灯
  36. public class RemoteControl{
  37. private Command command;
  38. public void setCommand(Command command){
  39. this.command = command;
  40. }
  41. public void pressButton(){
  42. command.execute();
  43. }
  44. }
  45. //调用命令模式
  46. public class Client{
  47. public static void main(String[] args){
  48. RemoteControl control = new RemoteControl();
  49. Light light = new Light();
  50. Command lightsOn = new LightsOnCommand(light);
  51. Command lightsOff = new LightsOffCommand(light);
  52. //设置开灯命令来开灯
  53. control.setCommand(lightsOn);
  54. control.pressButton();
  55. //设置关灯命令来关灯
  56. control.setCommand(lightsOff);
  57. control.pressButton();
  58. }
  59. }
解释器模式(Interpreter Pattern)

解释器模式是给定一种语言,定义其语法以及使用该语法来表示语言中句子的解释器。这种模式实现了一个表达式接口,该接口解释一个特定的上下文。这种模式被用在 SQL 解析、符号处理引擎等。它属于行为型模式。

它的优点:
1、可扩展性比较好,灵活。
2、增加了新的解释表达式的方式。
3、易于实现简单语法。

它的缺点:
1、可利用场景比较少。
2、对于复杂的文法比较难维护。
3、解释器模式会引起类膨胀。
4、解释器模式采用递归调用方法。

例如,我们用下面这个简单规则表达式的例子来更好的理解解释器模式。

</>复制代码

  1. //定义一个表达式接口
  2. public interface Expression {
  3. public boolean interpret(String context);
  4. }
  5. //定义一个基本规则的实现,输入的内容包含初始化的内容时,返回 true
  6. public class TerminalExpression implements Expression {
  7. private String data;
  8. public TerminalExpression(String data){
  9. this.data = data;
  10. }
  11. @Override
  12. public boolean interpret(String context) {
  13. if(context.contains(data)){
  14. return true;
  15. }
  16. return false;
  17. }
  18. }
  19. //定义一个或者规则的实现,输入的内容包含初始化时任意一个内容时,返回 true,否则 false
  20. public class OrExpression implements Expression {
  21. private Expression expr1 = null;
  22. private Expression expr2 = null;
  23. public OrExpression(Expression expr1, Expression expr2) {
  24. this.expr1 = expr1;
  25. this.expr2 = expr2;
  26. }
  27. @Override
  28. public boolean interpret(String context) {
  29. return expr1.interpret(context) || expr2.interpret(context);
  30. }
  31. }
  32. //定义一个并且规则的实现,输入的内容同时包含初始化时两个内容时,返回 true,否则 false
  33. public class AndExpression implements Expression {
  34. private Expression expr1 = null;
  35. private Expression expr2 = null;
  36. public AndExpression(Expression expr1, Expression expr2) {
  37. this.expr1 = expr1;
  38. this.expr2 = expr2;
  39. }
  40. @Override
  41. public boolean interpret(String context) {
  42. return expr1.interpret(context) && expr2.interpret(context);
  43. }
  44. }
  45. //调用规则表达式
  46. public class Client {
  47. //规则:Robert 和 John 是男性,输入只要满足其中一个规则就行
  48. public static Expression getMaleExpression(){
  49. Expression robert = new TerminalExpression("Robert");
  50. Expression john = new TerminalExpression("John");
  51. return new OrExpression(robert, john);
  52. }
  53. //规则:Julie 是一个已婚的女性,输入只要满足两个规则
  54. public static Expression getMarriedWomanExpression(){
  55. Expression julie = new TerminalExpression("Julie");
  56. Expression married = new TerminalExpression("Married");
  57. return new AndExpression(julie, married);
  58. }
  59. public static void main(String[] args) {
  60. Expression isMale = getMaleExpression();
  61. Expression isMarriedWoman = getMarriedWomanExpression();
  62. System.out.println("John is male? " + isMale.interpret("John"));
  63. System.out.println("Julie is a married women? "
  64. + isMarriedWoman.interpret("Married Julie"));
  65. }
  66. }

最后,还是那句话,每种设计模式有它自己的好处,也有它的坏处。在写代码时,多思考,想好在写,免得返工,先从思维方式上改变。使用单一原则,一个类,一个方法只做一件事情,这样方便维护,耦合低,可扩展。

PS:
清山绿水始于尘,博学多识贵于勤。
微信公众号:「清尘闲聊」。
欢迎一起谈天说地,聊代码。

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/74942.html

相关文章

  • 通熟易懂设计模式(一)

    摘要:维基百科抽象工厂的例子构建模式当构建一个复杂对象时,就可以使用建造者模式。在中,类中的方法就是适配器模式的例子,把一个数组转换为一个集合。这种设计模式的好处是方便添加一种车巴士,只需要继承类。 写在前面 评判一个程序员是否优秀,就是 show me the code。优秀的代码可读性强,高内聚低耦合,可扩展。想要写优秀的代码,做个优秀的程序员,就需要多看看大牛写的开源框架,吸取其中的精...

    luxixing 评论0 收藏0
  • Java后端学习,你应该看那些书籍?

    摘要:全书分三大部分共章第章介绍的基础知识安装和基本语法第章介绍的基本编程机器学习基础及中常用的第三方库函数,并介绍数据预处理的基本方法第章分别介绍常用的机器学习分析算法及深度学习等。每章都采用多个经典案例图文并茂地介绍机器学习的原理和实现方法。 最近在学习Java和全栈开发,推荐一些有用的书籍 书架主要针对Java后端和全栈开发用的 书籍介绍 《Spring Boot 2.0企业级应用开发...

    QiuyueZhong 评论0 收藏0
  • Java后端学习,你应该看那些书籍?

    摘要:全书分三大部分共章第章介绍的基础知识安装和基本语法第章介绍的基本编程机器学习基础及中常用的第三方库函数,并介绍数据预处理的基本方法第章分别介绍常用的机器学习分析算法及深度学习等。每章都采用多个经典案例图文并茂地介绍机器学习的原理和实现方法。 最近在学习Java和全栈开发,推荐一些有用的书籍 书架主要针对Java后端和全栈开发用的 书籍介绍 《Spring Boot 2.0企业级应用开发...

    FreeZinG 评论0 收藏0
  • 浅谈NUXT - 基于vue.js服务端渲染框架

    摘要:是一款基于的服务端渲染框架,跟的异曲同工。该配置项用于定义应用客户端和服务端的环境变量。 Vue因其简单易懂的API、高效的数据绑定和灵活的组件系统,受到很多前端开发人员的青睐。国内很多公司都在使用vue进行项目开发,我们正在使用的简书,便是基于Vue来构建的。 我们知道,SPA前端渲染存在两大痛点:(1)SEO。搜索引擎爬虫难以抓取客户端渲染的页面meta信息和其他SEO相关信息,使...

    yearsj 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<