JavaRush /Java 博客 /Random-ZH /Java 中的设计模式
Viacheslav
第 3 级

Java 中的设计模式

已在 Random-ZH 群组中发布
模式或设计模式是开发人员工作中经常被忽视的部分,使得代码难以维护和适应新的需求。我建议你看看它是什么以及它在JDK中是如何使用的。当然,所有以某种形式存在的基本模式已经在我们身边存在了很长时间。让我们在这篇评论中看看它们。
Java 中的设计模式 - 1
内容:

模板

职位空缺中最常见的要求之一是“了解模式”。首先,有必要回答一个简单的问题——“什么是设计模式?” Pattern从英文翻译过来就是“模板”。也就是说,这是我们做某事的某种模式。在编程中也是如此。有一些既定的最佳实践和方法来解决常见问题。每个程序员都是架构师。即使你只创建几个类甚至一个类,这也取决于你的代码在不断变化的需求下能存活多久,被其他人使用有多方便。这就是模板知识会有所帮助的地方,因为...... 这将使您能够快速了解​​如何最好地编写代码而不需要重写它。如您所知,程序员都是懒惰的人,立即写出好的东西比重做几次要容易)模式也可能看起来类似于算法。但他们有区别。该算法由描述必要操作的特定步骤组成。模式仅描述方法,但不描述实现步骤。图案不一样,因为... 解决不同的问题。通常区分以下类别:
  • 生成式

    这些模式解决了使对象创建灵活的问题

  • 结构性

    这些模式解决了有效建立对象之间联系的问题

  • 行为的

    这些模式解决了对象之间有效交互的问题

要考虑示例,我建议使用在线代码编译器 repl.it。
Java 中的设计模式 - 2

创作模式

让我们从对象生命周期的开始——对象的创建开始。生成模板有助于更方便地创建对象,并在此过程中提供灵活性。其中最著名的是“建造者”。该模式允许您逐步创建复杂的对象。在 Java 中,最著名的例子是StringBuilder
class Main {
  public static void main(String[] args) {
    StringBuilder builder = new StringBuilder();
    builder.append("Hello");
    builder.append(',');
    builder.append("World!");
    System.out.println(builder.toString());
  }
}
创建对象的另一种众所周知的方法是将创建转移到单独的方法中。这个方法就像一个对象工厂。这就是为什么该模式被称为“工厂方法”。以Java为例,其效果可以在类中看到java.util.Calendar。该类本身Calendar是抽象的,创建它的方法如下getInstance
import java.util.*;
class Main {
  public static void main(String[] args) {
    Calendar calendar = Calendar.getInstance();
    System.out.println(calendar.getTime());
    System.out.println(calendar.getClass().getCanonicalName());
  }
}
这通常是因为对象创建背后的逻辑可能很复杂。例如,在上面的例子中,我们访问基类Calendar,并且创建了该类GregorianCalendar。如果我们查看构造函数,我们可以看到根据条件创建不同的实现Calendar。但有时一种工厂方法是不够的。有时您需要创建不同的对象以使它们组合在一起。另一个模板将帮助我们完成这个任务 - “抽象工厂”。然后我们需要在一处创建不同的工厂。同时,优点是实现细节对我们来说并不重要,即 我们得到哪个具体工厂并不重要。最主要的是它创建了正确的实现。超级例子:
Java 中的设计模式 - 3
也就是说,根据环境(操作系统),我们将获得某个工厂来创建兼容的元素。作为通过其他人创建的方法的替代方法,我们可以使用“原型”模式。它的本质很简单——新的物体是按照现有物体的形象和相似性创建的,即 根据他们的原型。每个人都在 Java 中遇到过这种模式 - 这是接口的使用java.lang.Cloneable
class Main {
  public static void main(String[] args) {
    class CloneObject implements Cloneable {
      @Override
      protected Object clone() throws CloneNotSupportedException {
        return new CloneObject();
      }
    }
    CloneObject obj = new CloneObject();
    try {
      CloneObject pattern = (CloneObject) obj.clone();
    } catch (CloneNotSupportedException e) {
      //Do something
    }
  }
}
正如您所看到的,调用者不知道clone. 也就是说,基于原型创建对象是对象本身的责任。这很有用,因为它不会将用户与模板对象的实现联系起来。好吧,这个列表中的最后一个是“单例”模式。它的目的很简单——为整个应用程序提供对象的单个实例。这种模式很有趣,因为它经常显示多线程问题。要更深入地了解,请查看这些文章:
Java 中的设计模式 - 4

结构模式

随着对象的创建,它变得更加清晰。现在是研究结构模式的时候了。他们的目标是建立易于支持的类层次结构及其关系。第一个也是众所周知的模式是“代理”(Proxy)。代理与真实对象具有相同的接口,因此客户端通过代理或直接工作没有区别。最简单的例子是java.lang.reflect.Proxy
import java.util.*;
import java.lang.reflect.*;
class Main {
  public static void main(String[] arguments) {
    final Map<String, String> original = new HashMap<>();
    InvocationHandler proxy = (obj, method, args) -> {
      System.out.println("Invoked: " + method.getName());
      return method.invoke(original, args);
    };
    Map<String, String> proxyInstance = (Map) Proxy.newProxyInstance(
        original.getClass().getClassLoader(),
        original.getClass().getInterfaces(),
        proxy);
    proxyInstance.put("key", "value");
    System.out.println(proxyInstance.get("key"));
  }
}
正如您所看到的,在示例中我们有原始的 - 这是HashMap实现接口的示例Map。接下来,我们创建一个代理来替换客户端部分的原始代理HashMap,该代理调用put和方法get,在调用期间添加我们自己的逻辑。正如我们所看到的,模式中的交互是通过接口发生的。但有时替代品还不够。然后就可以使用“ Decorator ”模式了。装饰器也称为包装器或包装器。代理和装饰器非常相似,但是如果你看一下示例,你就会发现区别:
import java.util.*;
class Main {
  public static void main(String[] arguments) {
    List<String> list = new ArrayList<>();
    List<String> decorated = Collections.checkedList(list, String.class);
    decorated.add("2");
    list.add("3");
    System.out.println(decorated);
  }
}
与代理不同,装饰器将自身包装在作为输入传递的内容周围。代理既可以接受需要代理的内容,也可以管理代理对象本身的生命周期(例如,创建代理对象)。还有另一个有趣的模式——“适配器”。它类似于装饰器 - 装饰器将一个对象作为输入并返回该对象的包装器。不同之处在于,目标不是改变功能,而是使一个界面适应另一个界面。Java 有一个非常清楚的例子:
import java.util.*;
class Main {
  public static void main(String[] arguments) {
    String[] array = {"One", "Two", "Three"};
    List<String> strings = Arrays.asList(array);
    strings.set(0, "1");
    System.out.println(Arrays.toString(array));
  }
}
在输入处我们有一个数组。接下来,我们创建一个适配器,将数组引入接口List。当使用它时,我们实际上是在使用一个数组。因此,添加元素是行不通的,因为...... 原始数组无法更改。在这种情况下我们会得到UnsupportedOperationException. 开发类结构的下一个有趣方法是复合模式。有趣的是,使用一个界面的一组特定元素被排列在特定的树状层次结构中。通过调用父元素上的方法,我们可以在所有必要的子元素上调用该方法。此模式的一个主要示例是 UI(无论是 java.awt 还是 JSF):
import java.awt.*;
class Main {
  public static void main(String[] arguments) {
    Container container = new Container();
    Component component = new java.awt.Component(){};
    System.out.println(component.getComponentOrientation().isLeftToRight());
    container.add(component);
    container.applyComponentOrientation(ComponentOrientation.RIGHT_TO_LEFT);
    System.out.println(component.getComponentOrientation().isLeftToRight());
  }
}
正如我们所看到的,我们已经向容器添加了一个组件。然后我们要求容器应用组件的新方向。容器知道它由哪些组件组成,因此将该命令的执行委托给所有子组件。另一个有趣的模式是“”模式。之所以这样称呼它,是因为它描述了两个不同类层次结构之间的连接或桥梁。这些层次结构之一被视为抽象,另一个被视为实现。之所以突出显示这一点,是因为抽象本身不执行操作,而是将此执行委托给实现。当存在“控制”类和多种类型的“平台”类(例如,Windows、Linux 等)时,通常会使用此模式。通过这种方法,这些层次结构之一(抽象)将接收对另一个层次结构(实现)的对象的引用,并将主要工作委托给它们。因为所有实现都遵循一个公共接口,所以它们可以在抽象内互换。在 Java 中,一个明显的例子是java.awt
Java 中的设计模式 - 5
有关详细信息,请参阅文章“ Java AWT 中的模式”。在结构模式中,我还想提一下“ Facade ”模式。它的本质是将使用此 API 背后的库/框架的复杂性隐藏在方便简洁的界面后面。例如,您可以使用 JPA 中的 JSF 或 EntityManager 作为示例。还有另一种模式称为“享元”。它的本质是,如果不同的对象具有相同的状态,那么它可以被泛化,并且不是存储在每个对象中,而是存储在一个地方。然后每个对象将能够引用一个公共部分,这将减少存储的内存成本。这种模式通常涉及预缓存或维护对象池。有趣的是,我们从一开始就知道这种模式:
Java 中的设计模式 - 6
同样的道理,这里可以包含一个字符串池。您可以阅读有关此主题的文章:“享元设计模式”。
Java 中的设计模式 - 7

行为模式

因此,我们弄清楚了如何创建对象以及如何组织类之间的连接。剩下的最有趣的事情是提供改变对象行为的灵活性。行为模式将帮助我们做到这一点。最常提到的模式之一是“策略”模式。这就是《 Head First. Design Patterns 》一书中对模式的研究的开始。使用“策略”模式,我们可以在对象内存储我们将如何执行操作,即 里面的对象存储了一个可以在代码执行期间更改的策略。这是我们在使用比较器时经常使用的模式:
import java.util.*;
class Main {
  public static void main(String[] args) {
    List<String> data = Arrays.asList("Moscow", "Paris", "NYC");
    Comparator<String> comparator = Comparator.comparingInt(String::length);
    Set dataSet = new TreeSet(comparator);
    dataSet.addAll(data);
    System.out.println("Dataset : " + dataSet);
  }
}
在我们之前 - TreeSetTreeSet它具有维持元素顺序的行为,即 对它们进行排序(因为它是一个 SortedSet)。此行为有一个默认策略,我们在 JavaDoc 中看到:按“自然顺序”排序(对于字符串,这是字典顺序)。如果您使用无参数构造函数,就会发生这种情况。但如果我们想改变策略,我们可以通过Comparator。在这个例子中,我们可以将集合创建为new TreeSet(comparator),然后存储元素的顺序(存储策略)将更改为比较器中指定的顺序。有趣的是,有几乎相同的模式称为“状态”。“状态”模式表示,如果我们在主对象中有一些行为依赖于该对象的状态,那么我们可以将状态本身描述为一个对象并更改状态对象。并将调用从主对象委托给状态。通过学习 Java 语言的基础知识,我们了解到的另一种模式是“命令”模式。这种设计模式表明不同的命令可以表示为不同的类。该模式与策略模式非常相似。但在策略模式中,我们重新定义了如何执行特定操作(例如,排序TreeSet)。在“命令”模式中,我们重新定义将执行什么操作。当我们使用线程时,模式命令每天都伴随着我们:
import java.util.*;
class Main {
  public static void main(String[] args) {
    Runnable command = () -> {
      System.out.println("Command action");
    };
    Thread th = new Thread(command);
    th.start();
  }
}
如您所见,command 定义了将在新线程中执行的操作或命令。“责任链”模式也值得考虑。这个图案也非常简单。该模式表示,如果需要处理某些内容,那么您可以在链中收集处理程序。例如,这种模式经常用在 Web 服务器中。在输入处,服务器收到用户的一些请求。然后该请求将通过处理链。该处理程序链包括过滤器(例如,不接受来自 IP 地址黑名单的请求)、身份验证处理程序(仅允许授权用户)、请求标头处理程序、缓存处理程序等。但Java中有一个更简单、更容易理解的例子java.util.logging
import java.util.logging.*;
class Main {
  public static void main(String[] args) {
    Logger logger = Logger.getLogger(Main.class.getName());
    ConsoleHandler consoleHandler = new ConsoleHandler(){
		@Override
            public void publish(LogRecord record) {
                System.out.println("LogRecord обработан");
            }
        };
    logger.addHandler(consoleHandler);
    logger.info("test");
  }
}
如您所见,处理程序已添加到记录器处理程序列表中。当记录器收到要处理的消息时,每个此类消息都会通过logger.getHandlers该记录器的处理程序链(来自 )。我们每天看到的另一个模式是“迭代器”。它的本质是分离一个对象的集合(即代表数据结构的类。例如List)并遍历这个集合。
import java.util.*;
class Main {
  public static void main(String[] args) {
    List<String> data = Arrays.asList("Moscow", "Paris", "NYC");
    Iterator<String> iterator = data.iterator();
    while (iterator.hasNext()) {
      System.out.println(iterator.next());
    }
  }
}
正如您所看到的,迭代器不是集合的一部分,而是由遍历集合的单独类表示。迭代器的用户甚至可能不知道它正在迭代哪个集合,即 他正在参观什么藏品?访客模式也值得考虑。访问者模式与迭代器模式非常相似。此模式可帮助您绕过对象的结构并对这些对象执行操作。它们在概念上有所不同。迭代器遍历集合,这样使用迭代器的客户端并不关心集合里面是什么,只有序列中的元素是重要的。访问者意味着我们访问的对象存在一定的层次结构或结构。例如,我们可以使用单独的目录处理和单独的文件处理。Java 对此模式有一个开箱即用的实现,其形式如下java.nio.file.FileVisitor
import java.nio.file.*;
import java.nio.file.attribute.*;
import java.io.*;
class Main {
  public static void main(String[] args) {
    SimpleFileVisitor visitor = new SimpleFileVisitor() {
      @Override
      public FileVisitResult visitFile(Object file, BasicFileAttributes attrs) throws IOException {
        System.out.println("File:" + file.toString());
        return FileVisitResult.CONTINUE;
      }
    };
    Path pathSource = Paths.get(System.getProperty("java.io.tmpdir"));
    try {
      Files.walkFileTree(pathSource, visitor);
    } catch (AccessDeniedException e) {
      // skip
    } catch (IOException e) {
      // Do something
    }
  }
}
有时需要一些对象对其他对象的变化做出反应,那么“观察者”模式就会帮助我们。最方便的方法是提供订阅机制,允许某些对象监视并响应其他对象中发生的事件。这种模式经常用于对不同事件做出反应的各种侦听器和观察者。作为一个简单的例子,我们可以回顾一下 JDK 第一个版本中该模式的实现:
import java.util.*;
class Main {
  public static void main(String[] args) {
    Observer observer = (obj, arg) -> {
      System.out.println("Arg: " + arg);
    };
    Observable target = new Observable(){
      @Override
      public void notifyObservers(Object arg) {
        setChanged();
        super.notifyObservers(arg);
      }
    };
    target.addObserver(observer);
    target.notifyObservers("Hello, World!");
  }
}
还有另一种有用的行为模式——“中介者”。它很有用,因为在复杂的系统中,它有助于消除不同对象之间的连接,并将对象之间的所有交互委托给某个作为中介的对象。这种模式最引人注目的应用之一是 Spring MVC,它使用了这种模式。您可以在这里阅读更多相关内容:“ Spring:中介模式”。您经常可以在示例中看到相同的内容java.util.Timer
import java.util.*;
class Main {
  public static void main(String[] args) {
    Timer mediator = new Timer("Mediator");
    TimerTask command = new TimerTask() {
      @Override
      public void run() {
        System.out.println("Command pattern");
        mediator.cancel();
      }
    };
    mediator.schedule(command, 1000);
  }
}
该示例看起来更像是命令模式。而“Mediator”模式的本质就隐藏在“a”的实现中Timer。定时器里面有一个任务队列TaskQueue,有一个线程TimerThread。作为此类的客户,我们不与它们交互,而是与Timer对象交互,该对象响应我们对其方法的调用,访问它作为中介的其他对象的方法。从外观上看,它可能与“Facade”非常相似。但不同之处在于,当使用 Facade 时,组件不知道 Facade 的存在并相互通信。当使用“Mediator”时,组件知道并使用中介,但彼此不直接联系。值得考虑的是“模板方法”模式,该模式从其名称就可以看出来。最重要的是,代码的编写方式是为代码的用户(开发人员)提供一些算法模板,允许重新定义其步骤。这使得代码用户不必编写整个算法,而只需考虑如何正确执行该算法的一个或另一步骤。例如,Java 有一个抽象类AbstractList,它通过List. 然而,迭代器本身使用叶子方法,例如:get, set, remove。这些方法的行为由后代的开发人员确定AbstractList。因此, - 中的迭代器AbstractList是迭代工作表的算法的模板。特定实现的开发人员AbstractList通过定义特定步骤的行为来改变本次迭代的行为。我们分析的最后一个模式是“快照”(Momento)模式。它的本质是保存对象的某种状态并具有恢复该状态的能力。JDK 中最知名的示例是对象序列化,即 java.io.Serializable。让我们看一个例子:
import java.io.*;
import java.util.*;
class Main {
  public static void main(String[] args) throws IOException {
    ArrayList<String> list = new ArrayList<>();
    list.add("test");
    // Save State
    ByteArrayOutputStream stream = new ByteArrayOutputStream();
    try (ObjectOutputStream out = new ObjectOutputStream(stream)) {
      out.writeObject(list);
    }
    // Load state
    byte[] bytes = stream.toByteArray();
    InputStream inputStream = new ByteArrayInputStream(bytes);
    try (ObjectInputStream in = new ObjectInputStream(inputStream)) {
      List<String> listNew = (List<String>) in.readObject();
      System.out.println(listNew.get(0));
    } catch (ClassNotFoundException e) {
      // Do something. Can't find class fpr saved state
    }
  }
}
Java 中的设计模式 - 8

结论

正如我们从评论中看到的,模式多种多样。他们每个人都解决自己的问题。了解这些模式可以帮助您及时了解如何编写系统,使其灵活、可维护且不易更改。最后,一些更深入研究的链接: #维亚切斯拉夫
评论
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION