Роль шаблона проектирования
Шаблоны хороши, но не усложняют ли они код?
Сложность кода часто субъективна. То, насколько люди знакомы с шаблонами, сильно влияет на то, как они относятся к рефакторингу на основе шаблонов. Когда они не знакомы с шаблоном, они думают, что он слишком сложен, а когда они знакомы с шаблоном, они обычно этого не делают.
Шаблон — это кристаллизация мудрости предшественников, а шаблон — это решение определенного типа проблемы в определенном контексте. Повторное использование этой мудрости может быть очень полезным. Часто внедрение шаблонов помогает устранить дублирование, упростить логику и повысить гибкость и расширяемость. ** Постарайтесь выучить как можно больше паттернов, не используя паттерны, потому что они слишком сложны.
Использовать паттерн в случае или нет — очень запутанный вопрос для всех разработчиков. Иногда из-за того, что предвидятся какие-то изменения в требованиях, используется определенный шаблон проектирования для гибкости и масштабируемости системы, но предвиденных требований нет.Наоборот, приходят непредвиденные требования.Немало, что приводит к использованию шаблонов проектирования, которые имеют противоположный эффект при изменении кода, так что вся команда проекта жалуется. С такими примерами, я думаю, сталкивался каждый программист.Поэтому, исходя из принципов гибкой разработки, когда мы проектируем программу, если она может быть хорошо решена без использования определенного шаблона в соответствии с текущими требованиями, то мы не должны его вводить, потому что ввести шаблон проектирования несложно. , мы можем преобразовать систему и внедрить этот шаблон проектирования, когда это действительно необходимо.
Например, клиентский модуль публикации обменных курсов оптимизирован для режима наблюдателя, который можно оптимизировать (при необходимости), когда это действительно необходимо в итеративном процессе. Если сначала не подумать о необходимости, легко внести в систему ненужную сложность.
Общие шаблоны проектирования
В этой статье мы разобрали в общей сложности 15 распространенных шаблонов проектирования.Если вы чувствуете, что запомнить их нелегко, вы можете попробовать метод эпизодической памяти. Редактирование шаблонов проектирования в каждой категории в виде небольшой сцены может облегчить запоминание и запомнить его более четко.
Творчество
в разгарефабрика, вдруг позвонил менеджерХолостякЧжан Гоподниматьна чертежепрототип.
синглтон
Одиночки имеют свои собственные уникальные сценарии использования, как правило, для тех случаев, когда бизнес-логика ограничена несколькими экземплярами, и только одиночки, такие как существование счетчиков, обычно должны использовать экземпляр для записи, если имеется более одного экземпляра. быть неточным.
- голодный китаец
Почему ты голоден? Тот, кто голоден, не выбирает пищу, если есть еда, он должен срочно ее съесть. Синоним здесь: синглтон класса создается, когда класс загружается и сохраняется в классе.
public class Singleton {
// 首先,将 new Singleton() 堵死
private Singleton() {};
// 创建私有静态实例,意味着这个类第一次使用的时候就会进行创建
private static Singleton instance = new Singleton();
public static Singleton getInstance() {
return instance;
}
// 瞎写一个静态方法。这里想说的是,如果我们只是要调用 Singleton.getDate(...),
// 本来是不想要生成 Singleton 实例的,不过没办法,已经生成了
public static Date getDate(String mode) {return new Date();}
}
Недостатки: неленивая загрузка, пустая трата памяти
- ленивый
//写法1,对getInstance加Synchronized锁,并发访问效率较低。
public class LHanDanli {
//定义一个私有类变量来存放单例,私有的目的是指外部无法直接获取这个变量,而要使用提供的公共方法来获取
private static LHanDanli dl = null;
//定义私有构造器,表示只在类内部使用,亦指单例的实例只能在单例类内部创建
private LHanDanli(){}
//定义一个公共的公开方法来返回该类的实例,由于是懒汉式,需要在第一次使用时生成实例,所以为了线程安全,使用synchronized关键字来确保只会生成单例
public static synchronized LHanDanli getInstance(){
if(dl == null){
dl = new LHanDanli();
}
return dl;
}
}
//写法2,双重判断法(Double Check Lock,DCL)。
public class Singleton {
// 首先,也是先堵死 new Singleton() 这条路
private Singleton() {}
// 和饿汉模式相比,这边不需要先实例化出来,注意这里的 volatile,它是必须的
private static volatile Singleton instance = null;
public static Singleton getInstance() {
if (instance == null) {
// 加锁
synchronized (Singleton.class) {
// 这一次判断也是必须的,不然会有并发问题
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
}
Двойное суждение здесь относится к двоякому суждению, а блокировка относится к синхронизированному.Зачем нужно выносить двойное суждение?На самом деле это очень просто.Первое суждение состоит в том,что если единичный экземпляр уже существует,то вам больше не нужно выполнять операцию синхронизации, а напрямую возвращать этот экземпляр. Если он не создан, он войдет в синхронизированный блок. Цель синхронизированного блока такая же, как и раньше. Цель состоит в том, чтобы предотвратить создание нескольких экземпляров при одновременном выполнении двух вызовов. С синхронизированным блоком, только один поток может вызывать функцию одновременно Доступ к содержимому синхронизированного блока, который создается, когда первый вызов для захвата блокировки получает экземпляр.
Преимущество режима DCL заключается в том, что он создается только тогда, когда объект необходимо использовать.Чтобы избежать ненужной блокировки, когда INSTANCE == null оценивается в первый раз, экземпляр блокируется, а затем создается экземпляр при его загрузке для первый раз. Это не только экономит место в памяти, но и обеспечивает потокобезопасность. ** Однако из-за неупорядоченной функции выполнения JVM DCL также будет небезопасным для потоков. ** Конкретный анализ выглядит следующим образом:
instance = new SingleTon();
Этот шаг, по сути, делится на три этапа выполнения jvm:
1. Освободите память в куче памяти.
2. Создайте экземпляр каждого параметра в SingleTon в куче памяти.
3. Укажите переменной место в куче памяти.
Из-за функции выполнения jvm не по порядку, 3 может быть выполнено до выполнения 2. Если в это время он переключится на поток B, поскольку выполняется 3, экземпляр не пуст и будет напрямую взят для использования. ., в этом случае произойдет исключение. Это известная проблема отказа DCL. Однако после JDK1.5 официальная также обнаружила эту проблему, поэтому была указана volatile, то есть в JDK1.6 и более поздних версиях, пока она определяется как private volatile static SingleTon INSTANCE = null; проблема сбоя DCL может быть решено. Volatile гарантирует, что INSTANCE каждый раз считывается из основной памяти, что немного снижает эффективность, но не вредит.
- Шаблон статического внутреннего класса (рекомендуется)
public class SingleTon{
private SingleTon(){}
private static class SingleTonHoler{
private static SingleTon INSTANCE = new SingleTon();
}
public static SingleTon getInstance(){
return SingleTonHoler.INSTANCE;
}
Преимущество статического внутреннего класса заключается в том, что внутренний класс не нужно загружать сразу при загрузке внешнего класса, а INSTANCE не будет инициализирован, если внутренний класс не загружен, поэтому он не занимает память. То есть при первой загрузке SingleTon нет необходимости загружать SingleTonHoler.Только при первом вызове метода getInstance() будет инициализирован INSTANCE.Первый вызов метода getInstance() будет заставить виртуальную машину загрузить класс SingleTonHoler., этот метод не только обеспечивает безопасность потоков, но и гарантирует уникальность синглтона, а также задерживает создание экземпляра синглтона.
Недостаток: нельзя передавать параметры
Ссылаться на:blog.CSDN.net/Beauty Bar 65482/AR…
Наилегчайший вес
Когда мы создаем много объектов в нашем проекте, и эти объекты имеют много одинаковых модулей, мы можем извлечь эти одинаковые модули и использовать режим легковеса для создания одного объекта, а затем использовать этот объект для взаимодействия со многими предыдущими объектами, чтобы Несомненно, это сэкономит много места.
woo woo woo.cn blog on.com/V1good song/afraid/6…
фабрика
Фабрика – это место, где производится продукция.
Используя концепцию фабрики в шаблонах проектирования Java, именно здесь создаются объекты.
Зачем добавлять фабричный класс к объекту, который можно создать напрямую?
Для этого нужно понять, какую проблему должен решить фабричный метод.Если есть только один класс, мы можем просто создать новый объект и закончить работу, что проще всего, но что, если классов несколько, и эти классы также должны быть адаптированы к различным ситуациям.Для создания различных объектов в настоящее время необходима фабрика.Мы можем создавать конкретные объекты в соответствии с условиями на фабрике.
Таким образом, вызывающий объект отделяется от конкретного целевого класса, вызывающий объект вообще не знает, какой объект нужно создать, он просто выдвигает условия, а затем фабрика может решить, какой объект создать на основе заданных условий.
- Простая фабрика (фабрика производит одноклассный продукт, корпус переключателя)
/**
* 桌子接口
*/
public interface Desk {
String getType();
}
/**
* 木质桌子
*/
public class WoodenDesk implements Desk{
private String type = "木质桌";
@Override
public String getType() {
return type;
}
}
/**
* 塑料桌
*/
public class PlasticDesk implements Desk {
private String type = "塑料桌";
@Override
public String getType() {
return type;
}
}
/**
* 桌子工厂
*/
public class DeskFactory {
public static Desk createDesk(Type type) {
switch (type) {
case WOODEN:
return new WoodenDesk();
case PLASTIC:
return new PlasticDesk();
default:
return null;
}
}
}
/**
* 测试类
*/
public class Client {
public static void main(String[] args) {
Desk desk = DeskFactory.createDesk(Type.PLASTIC);
System.out.println(desk.getType());
}
}
Проще говоря, простой фабричный шаблон обычно выглядит следующим образом: фабричный класс XxxFactory имеет статический метод, который возвращает разные объекты-экземпляры, производные от одного и того же родительского класса (или реализующие один и тот же интерфейс) в соответствии с нашими разными параметрами.
- Фабричный метод (фабрика производит один тип продукта, новые продукты не нужно модифицировать исходную фабрику)
Реализация абстрактного фабричного класса, каждый конкретный фабричный класс производит только один продукт. Простой фабричный метод имеет только один фабричный класс для реализации нескольких целей (продуктов). Когда целевая реализация (продукт) увеличивается, нам приходится модифицировать метод фабричного класса, чтобы сделать его совместимым с новым типом реализации, что явно нарушает принцип открытого-закрытого, поэтому появляется паттерн фабричный метод.
-
Абстрактная фабричная модель (фабрика производит ассортимент продукции)
-
Зачем вводить абстрактную фабрику
Шаблон абстрактной фабрики является обновлением шаблона фабричных методов, но сценарии, с которыми они сталкиваются, немного отличаются.
Целью шаблона фабричный метод обычно является один класс, например, в примере фабричного метода целью является товар, такой как таблица.
Что, если это так: то, что производится, представляет собой комбинацию столов и стульев, набор товаров для цели, и виды каждого вида товаров в каждом наборе товаров разные, и разные комбинации образуют разные наборы. В этом случае вам нужно использовать шаблон абстрактной фабрики.
- Разница между абстрактной фабрикой и фабричным методом
Абстрактный фабричный паттерн сталкивается с композицией, и если ее исключить, остальные аспекты выглядят аналогично.
пример
Классический пример — сборка компьютера. Давайте не будем вводить шаблон абстрактной фабрики и посмотрим, как его реализовать.
Поскольку компьютер состоит из многих компонентов, мы абстрагируем ЦП и материнскую плату, затем ЦП создается фабрикой CPUFactory, а материнская плата создается фабрикой MainBoardFactory, а затем мы объединяем ЦП и материнскую плату, как показано ниже:
Вызов клиента в это время выглядит следующим образом:
// 得到 Intel 的 CPU
CPUFactory cpuFactory = new IntelCPUFactory();
CPU cpu = intelCPUFactory.makeCPU();
// 得到 AMD 的主板
MainBoardFactory mainBoardFactory = new AmdMainBoardFactory();
MainBoard mainBoard = mainBoardFactory.make();
// 组装 CPU 和主板
Computer computer = new Computer(cpu, mainBoard);
Глядя на фабрику ЦП и фабрику материнской платы по отдельности, мы видим, что это то, о чем мы говорили ранее.заводской узор. Этот метод также легко расширить, потому что, если вы хотите добавить жесткий диск к компьютеру, вам нужно только добавить HardDiskFactory и соответствующую реализацию, и нет необходимости модифицировать существующую фабрику.
Однако у этого подхода есть проблема, заключающаяся в том, что еслиПроцессоры Intel и материнские платы AMD несовместимы, то код подвержен ошибкам, поскольку клиент не знает, что они несовместимы, и будет ошибочно создавать произвольные комбинации.
Вот что мы должны сказатьСемейство продуктов, который представляет собой набор аксессуаров, составляющих продукт:
Когда дело доходит до проблемы такого семейства продуктов, для поддержки необходим шаблон абстрактной фабрики. Мы больше не определяем фабрики ЦП, фабрики материнских плат, фабрики жестких дисков, фабрики дисплеев и т. д., мы напрямую определяем фабрики компьютеров, и каждая фабрика компьютеров отвечает за производство всего оборудования, что гарантирует отсутствие проблем совместимости.
В настоящее время клиенту больше не нужно выбирать производителя процессора, производителя материнской платы, производителя жесткого диска и т. д., а напрямую выбирать фабрику бренда.Фабрика бренда будет нести ответственность за все производство, и могут быть гарантированно совместимы и доступны.
public static void main(String[] args) {
// 第一步就要选定一个“大厂”
ComputerFactory cf = new AmdFactory();
// 从这个大厂造 CPU
CPU cpu = cf.makeCPU();
// 从这个大厂造主板
MainBoard board = cf.makeMainBoard();
// 从这个大厂造硬盘
HardDisk hardDisk = cf.makeHardDisk();
// 将同一个厂子出来的 CPU、主板、硬盘组装在一起
Computer result = new Computer(cpu, board, hardDisk);
}
Конечно, очевидна и проблема абстрактных фабрик, например, если мы хотим добавить дисплей, нам нужно модифицировать все фабрики и добавить ко всем фабрикам методы изготовления мониторов. Это небольшое нарушениеЗакрыто для модификации, открыто для расширенияэтот принцип проектирования.
Ссылаться на:
woo woo woo.cn blog on.com/V1good song/afraid/1…
строитель
woo woo woo.cn blog on.com/V1good song/afraid/1…
Шаблон построителя предназначен для работы с классами со многими свойствами, чтобы сделать код более элегантным. Это кажется бесполезным.
прототип
Шаблон прототипа очень прост: есть экземпляр-прототип, и на основе этого экземпляра-прототипа генерируется новый экземпляр, то есть «клон». В классе Object есть метод clone(), который используется для создания нового объекта. Конечно, если мы хотим вызвать этот метод, Java требует, чтобы наш класс сначала реализовал интерфейс Cloneable. Этот интерфейс не определяет никаких методы, но не делайте этого. Если это так, CloneNotSupportedException будет сгенерировано во время clone(). Клон Java - это поверхностный клон.Когда встречается ссылка на объект, клонированный объект и ссылка в исходном объекте будут указывать на один и тот же объект. Обычный способ реализации глубокого клонирования — это сериализация объекта, а затем его десериализация.
import java.io.Serializable;
import java.util.Arrays;
/**
* ClassName: UsersEntity
* Description: Object类的clone方法
* ①实现Cloneable接口
* ②重写clone方法
* 浅拷贝绝对正确,但是在面对一些复杂类型的属性时候,可能无法进行深拷贝
* 要实现真正的深拷贝,需要根据特定的实体类类型去做,我还无法深刻理解,所以这里就不列出了
*
* 这里还实现了Serializable接口时为了演示另一种克隆方法
*/
public class UsersEntity implements Cloneable,Serializable{
int age;
int[] number;
public UsersEntity(int age, int[] number) {
this.age = age;
this.number = number;
}
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
@Override
public String toString() {
return "UsersEntity{" +
"age=" + age +
", number=" + Arrays.toString(number) +
'}';
}
import java.io.*;
/**
* ClassName: CloneUtil
*/
public class CloneUtil {
/**
* 实现对象克隆方法
* 1)实体类实现Cloneable接口并重写Object类的clone()方法
* 2)实体类实现Serializable接口,Serializable接口没有任何抽象方法,只是起到一个标记作用
* 使得实体类能够被序列号,然后通过对象的序列化和反序列化进行克隆
*/
public static <T extends Serializable> T clone(T obj) throws IOException, ClassNotFoundException {
ByteArrayOutputStream bout = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(bout);
oos.writeObject(obj);
ByteArrayInputStream bin = new ByteArrayInputStream(bout.toByteArray());
ObjectInputStream ois = new ObjectInputStream(bin);
return (T) ois.readObject();
}
public class TestClone {
public static void main(String[] args) throws IOException, ClassNotFoundException {
int[] number = {5,3,56};
int age = 123;
// 克隆方法①:实现Object类的clone方法,只能进行浅拷贝,如果拷贝失败可能会在运行时抛出异常
UsersEntity user = new UsersEntity(age,number);
System.out.println(user);
// 克隆方法②:基于序列化和反序列化实现的不仅仅是深度克隆,更重要的是通过泛型限定,可以检查出要克隆的对象是否支持序列化
// 这项检查是在编译器完成的,不是运行时抛出异常,优于方法①,让问题再编译的时候暴露出来比运行时抛出好
UsersEntity userClone = CloneUtil.clone(user);
System.out.println(userClone);
}
}
структурный
недвижимостьиграет рольвынуть компьютер,адаптерподключен к большому экрану. «Посмотрите на чужие домаВнешний видиУкрасить«Его необходимо отремонтировать, прежде чем его можно будет продать.
Адаптер (преобразование интерфейса)
- Зачем вводить шаблон адаптера?
Преобразуйте интерфейс класса в другой интерфейс, который хочет клиент. Как правило, клиенты могут получить доступ к службам, которые он предоставляет, через интерфейс целевого класса. Иногда существующий класс может удовлетворить функциональные потребности клиентского класса, но предоставляемый им интерфейс не обязательно соответствует ожиданиям клиентского класса.Это может быть связано с тем, что имя метода в существующем классе не соответствует имени метода, определенному в целевом классе. класс и т.п. вызванный.
- Диаграмма классов
- экземпляр объектного адаптера
//目标接口(客户端需要使用的接口)
public interface Target {
//客户端需要请求处理的方法
public void request();
}
public class Adapter implements Target {
//持有源接口对象
private Adaptee adaptee;
/**
* 构造方法,传入需要被适配的对象
* @param adaptee
*/
public Adapter(Adaptee adaptee) {
this.adaptee = adaptee;
}
/**
* 重写目标接口的方法,以适应客户端的需求
*/
@Override
public void request() {
//调用源接口的方法
System.out.println("适配器包装源接口对象,调用源接口的方法");
adaptee.specifiRequest();
}
}
public class Client {
public static void main(String[] args){
//创建源对象(被适配的对象)
Adaptee adaptee = new Adaptee();
//利用源对象对象一个适配器对象,提供客户端调用的方法
Adapter adapter = new Adapter(adaptee);
System.out.println("客户端调用适配器中的方法");
adapter.request();
}
}
//客户端调用适配器中的方法
//适配器包装源接口对象,调用源接口的方法
//源接口对象调用源接口中的方法
- Экземпляр интерфейсного адаптера
Когда есть такой интерфейс, в котором определено более N методов, и мы хотим использовать только один или несколько методов, если мы напрямую реализуем интерфейс, тогда мы должны реализовать все методы, даже если мы только Пустые методы, которые не нужно (просто написать пару фигурных скобок, не реализовывать специфические методы) также сделает этот класс раздутым и неудобным для вызова.В это время мы можем использовать абстрактный класс в качестве промежуточного программного обеспечения, а именно адаптера, Используйте этот абстрактный класс для реализовать интерфейс, и все методы в абстрактном классе пусты, то мы создаем унаследованный класс абстрактного класса и переписываем методы, которые нам нужно использовать.
public interface A {
void a();
void b();
void c();
void d();
void e();
void f();
}
public abstract class Adapter implements A {
public void a(){}
public void b(){}
public void c(){}
public void d(){}
public void e(){}
public void f(){}
}
public class Ashili extends Adapter {
public void a(){
System.out.println("实现A方法被调用");
}
public void d(){
System.out.println("实现d方法被调用");
}
}
public class Client {
public static void main(String[] args) {
A a = new Ashili();
a.a();
a.d();
}
}
woo woo woo.cn blog on.com/V1good song/afraid/6…
сегмент fault.com/ah/119000001…
Фасад
- Экранирование компонентов подсистемы от клиентов уменьшает количество объектов, которые обрабатывают клиенты, и упрощает использование подсистемы. Благодаря внедрению шаблона Фасад клиентский код будет простым, с несколькими связанными с ним объектами.
- Реализована слабая связь между подсистемой и клиентом, благодаря чему изменение компонента подсистемы не влияет на клиентский класс, который ее вызывает, требуется только корректировка класса внешнего вида.
Уууу. Видите лазейки. Может /переварить/ ОК…
woo woo woo.cn blog on.com/V1good song/afraid/6…
Украсить
- Зачем вводить режим украшения
Шаблон декоратора используется, когда существующей целевой функции недостаточно и ее необходимо улучшить, при условии, что цель имеет абстрактный интерфейс.Для динамического добавления некоторых дополнительных обязанностей к объекту с точки зрения добавления объектных функций шаблон оформления является более гибким, чем создание реализаций подкласса.
Режим украшения может динамически добавлять функции к объектам, то есть добавлять функции к объектам извне объекта. Обычно есть два способа добавить поведение к классу или объекту:
-
Механизм наследования.Использование механизма наследования является эффективным способом добавления функций к существующим классам.Наследуя существующий класс, подклассы могут иметь свои собственные методы, а также иметь методы родительских классов. Но этот подход статичен, и пользователь не может контролировать, как и когда будет добавлено поведение.
-
Механизм композиции заключается во встраивании объекта одного класса в другой объект, а другой объект решает, вызывать ли поведение вложенного объекта, чтобы расширить собственное поведение.Такой вложенный объект мы называем декоратором (Decorator).
-
пример
/**
* 目标接口:房子
*/
public interface House {
void output();
}
/**
* 房子实现类
*/
public class DonghaoHouse implements House {
@Override
public void output() {
System.out.println("这是董浩的房子");
}
}
/**
* 房子实现类
*/
public class DongliangHouse implements House {
@Override
public void output() {
System.out.println("这是董量的房子");
}
}
public class Decorator implements House {
private House house;
public Decorator(House house){
this.house = house;
}
@Override
public void output() {
System.out.println("这是针对房子的前段装饰增强");
house.output();
System.out.println("这是针对房子的后段装饰增强");
}
}
public class Clienter {
public static void main(String[] args) {
House donghaoHouse = new DonghaoHouse();
House decorator = new Decorator(donghaoHouse);
decorator.output();
}
}
играет роль
- Зачем вводить прокси-режим?
Прокси (Proxy) заключается в предоставлении прокси другим объектам для управления доступом к объекту, то есть для доступа к целевому объекту через прокси-объект. Преимущество этого в том, что дополнительные функциональные операции могут быть расширены на основе реализации целевого объекта, то есть функция целевого объекта может быть расширена.
Вот мысль в программировании: не изменяйте код или методы, написанные другими, по своему желанию.Если вам нужно изменить его, вы можете расширить метод по доверенности.
Агентская модель — это модель, с которой я соприкасался ранее: агентство — это посредник, посредник. Существуют также агенты по закону, такие как поверенные и т. д. Клиент доверяет часть своих полномочий агенту, а агент имеет некоторые полномочия принципала (принципала) и может осуществлять эти полномочия от имени принципала. на этот раз агент эквивалентен принципалу.Конечно, агент также может сотрудничать со своими способностями при осуществлении полномочий, конечно, он не может превышать эти полномочия.
Режим прокси в Java похож на прокси выше, мы также создаем прокси-класс для класса (класс делегирования), чтобы предоставлять внешние функции от его имени.
- Как создать прокси-класс для класса в Java?
Очень просто, нам нужно создать публичный интерфейс, класс делегата должен реализовать этот интерфейс, а затем создать класс реализации интерфейса в качестве прокси-класса.Методы в этом классе могут напрямую вызывать одноименный метод в класс делегата, и внешний класс должен получить к нему доступ. , вы можете использовать интерфейс, чтобы указать на экземпляр класса прокси, вызвать метод в классе прокси и косвенно вызвать конкретную реализацию метода в классе делегата.
Одним из преимуществ прокси-режима является то, что он предоставляет метод унифицированного интерфейса для внешнего мира, а прокси-класс реализует дополнительные рабочие режимы реального класса в интерфейсе, так что систему можно расширять, не затрагивая внешние вызовы. То есть, когда я хочу изменить реальную операцию персонажа, я стараюсь не изменять его, а выполнять дополнительные действия на уровне «пакета» извне, то есть в прокси-классе. Например: интерфейс A имеет интерфейсный метод operator(), реальная роль: RealA реализует интерфейс A, он должен реализовать интерфейсный метод operator(). Клиент Client вызывает интерфейсный метод operator() интерфейса A. Теперь появилось новое требование, и поведение оператора() в RealA необходимо изменить. Как это сделать? Если RealA изменить, это повлияет на стабильность исходной системы, и ее необходимо будет повторно протестировать. Это необходимость в прокси-классах для реализации дополнительных операций поведения. Создайте прокси-сервер ProxyA для реализации интерфейса A и внедрите в него реальный объект RealA. ProxyA реализует метод интерфейса operator(), можно добавить дополнительное поведение, а затем вызывается operator() реального объекта. Таким образом достигается «закрытость для модификации и открытость для расширения», что обеспечивает стабильность системы. Мы видим, что клиентский вызов Client по-прежнему является интерфейсным методом operator() интерфейса A, но экземпляр стал классом ProxyA. То есть режим прокси реализует принцип ocp.
Когда мы пишем функциональную функцию, нам часто нужно написать код, который не имеет прямого отношения к функции, но очень необходим, например, ведение журнала, отправка информации, безопасность и поддержка транзакций и т. д. Хотя эти тривиальные коды необходимы, они принесут следующие неприятности:
- Второстепенный код находится вне функционального кода, это не цель функции, это перерыв в ООП.
- Код ветвления заставит функциональный код зависеть от других классов, углубит связь между классами и уменьшит возможность повторного использования.
- С юридической точки зрения, побочный код должен отслеживать функциональный код, а затем предпринимать действия, а не функциональный код, сообщающий побочному коду о необходимости выполнения действия.
В таком сценарии вы можете поместить тривиальный код в прокси-класс и выглядеть как функциональное расширение.
- статический прокси
-
Преимущества: целевая функция может быть расширена без изменения функции целевого объекта.
-
Недостатки: Прокси-класс и класс делегата реализуют один и тот же интерфейс и реализуют одни и те же методы. Это приводит к большому дублированию кода. Если интерфейс добавляет метод, помимо того, что все реализующие классы должны реализовать этот метод, все прокси-классы также должны реализовать этот метод. Увеличивает сложность обслуживания кода.
public interface IUserDao {
void doSth();
}
public class UserDao implements IUserDao {
public void doSth() {
System.out.println("----已经保存数据!----");
}
}
public class UserDaoProxy implements IUserDao{
//接收保存目标对象
private IUserDao iUserDao;
public UserDaoProxy(IUserDao iUserDaoiUserDao){
this.iUserDao = iUserDao;
}
public void save() {
System.out.println("开始事务...");
iUserDao.doSth();//执行目标对象的方法
System.out.println("提交事务...");
}
}
public class Test {
public static void main(String[] args) {
//委托对象
UserDao userDao = new UserDao();
//代理对象,把委托对象传给代理对象,建立代理关系
UserDaoProxy proxy = new UserDaoProxy(userDao);
proxy.doSth();//执行的是代理的方法
}
}
- Разница между режимом декоратора и режимом прокси
- Агент является дискреционным агентом, а цель вовсе не является внешней, и все это делается классом агента.
- Украшение — это улучшение и вспомогательное средство, цель все еще может сама оказывать услуги внешнему миру, а декоратор играет лишь роль улучшения.
- Хотя и режим прокси, и режим декоратора зависят от целевого интерфейса, целевой класс реализации для прокси фиксирован, а режим декоратора можно указать по желанию, то есть цель может быть расширена сама по себе. (То есть метод в декораторе может быть расширен, а метод в прокси-классе должен соответствовать исходному методу класса-делегата)
woo woo woo.cn blog on.com/V1good song/afraid/1…
поведенческий
тренер упалЗаказ: "Сегодня слишком грязногосударствоКак это так плохо! "СледующееСтратегияпросто блянаблюдатькоманда соперника, относитесь к ним как кшаблон, и посмотрите, как другая сторона следуетобязанностьКаждый занимается своим делом! .
Шаблон (абстрактный класс)
- Почему шаблон метода шаблона?
Подготовьте абстрактный класс, реализуйте часть логики в конкретных методах и конкретных конструкторах, а затем объявите некоторые абстрактные методы, чтобы заставить подклассы реализовать остальную часть логики. Различные подклассы могут по-разному реализовывать эти абстрактные методы и, таким образом, иметь разные реализации оставшейся логики. Вот для чего предназначен шаблон метода шаблона.
1) Хорошая инкапсуляция: инкапсулировать общедоступные инвариантные методы в родительском классе, а подкласс отвечает за реализацию конкретной логики.
2) Хорошая расширяемость: добавленная функция расширяется подклассом для реализации базового метода, который соответствует принципу единой ответственности и принципу открытия и закрытия.
3) Повторное использование кода.
- Диаграмма классов
- Пример - разблокировка маленькой желтой машины
public abstract class AbstractClass {
protected boolean isNeedUnlock = true; // 默认需要开锁
/**
* 基本方法,子类需要实现
*/
protected abstract void unlock();
/**
* 基本方法,子类需要实现
*/
protected abstract void ride();
/**
* 钩子方法,子类可实现
*
* @param isNeedUnlock
*/
protected void isNeedUnlock(boolean isNeedUnlock) {
this.isNeedUnlock = isNeedUnlock;
}
/**
* 模板方法,负责调度基本方法,子类不可实现
*/
public final void use() {
if (isNeedUnlock) {
unlock();
} else {
System.out.println("========锁坏了,不用解锁========");
}
ride();
}
}
// 扫码开锁的单车
public class ScanBicycle extends AbstractClass {
@Override
protected void unlock() {
System.out.println("========" + "扫码开锁" + "========");
}
@Override
protected void ride() {
System.out.println(getClass().getSimpleName() + "骑起来很拉风");
}
protected void isNeedUnlock(boolean isNeedUnlock) {
this.isNeedUnlock = isNeedUnlock;
}
}
// 密码开锁的单车
public class CodeBicycle extends AbstractClass {
@Override
protected void unlock() {
System.out.println("========" + "密码开锁" + "========");
}
@Override
protected void ride() {
System.out.println(getClass().getSimpleName() + "骑起来很拉风");
}
protected void isNeedUnlock(boolean isNeedUnlock) {
this.isNeedUnlock = isNeedUnlock;
}
}
//调用测试1
public class Client {
public static void main(String[] args) {
ScanBicycle scanBicycle = new ScanBicycle();
scanBicycle.use();
CodeBicycle codeBicycle = new CodeBicycle();
codeBicycle.use();
}
}
//调用测试2,测试钩子方法
public class Client {
public static void main(String[] args) {
ScanBicycle scanBicycle = new ScanBicycle();
scanBicycle.isNeedUnlock(false);
scanBicycle.use();
CodeBicycle codeBicycle = new CodeBicycle();
codeBicycle.isNeedUnlock(true);
codeBicycle.use();
}
}
Команда
Модуль исполнения стратегии формирования обменного курса может использовать этот режим, принимая контекст в качестве единого параметра.
/定义一个命令接口
public interface Command{
public void execute();
public void undo();
}
//其中的一个命令继承自这个接口
public class addPatCommand implements Command{
public void execute(){
doSomething...
}
public void undo(){
undo...
}
}
//执行者,用来执行命令
public class Executer{
//命令队列
ArrayList<Command>commandList = new ArrayList<Command>();
//记录当前已经执行的命令
int executed = 0;
//执行命令
public void execute(){
if(commandList.size()==0)
System.out.println("Please add a command");
else{
for(int i=executed; i<commandList.size(); i++){
commandList.get(i).execute();
}
executed = commandList.size();
}
}
//添加命令
public void addCommand(Command mCommand){
commandList.add(mCommand);
}
//执行撤销命令,回滚到执行前
public void undo(){
if(executed==0)
System.out.println("Do Nothing");
else{
for(int i=executed; i>=0; i--){
commandList.get(i).undo();
}
}
}
}
сегмент fault.com/ah/119000000…
no.OSCHINA.net/рассказать/было бы…
Стратегия (скачки Тянь Цзи или фестивальный маркетинг)
- Зачем нужен режим стратегии
Режим стратегии — это режим поведения объекта, по сути, это инкапсуляция ряда одноуровневых алгоритмов, не заботящаяся о реализации алгоритма, и позволяющая клиенту динамически полагаться на «окружение». класс для выбора требуемого алгоритма, потому что они могут заменять друг друга и могут. Говорят, что режим стратегии позволяет серии алгоритмов плавно переключаться.
- Диаграмма классов
//结合工厂模式
public class StrategyFactory {
private Strategy strategy;
// 把创建策略放在封装角色内,客户端只需要知道结果
public void build(String strategyType) {
if (strategyType.equals("WIN")) {
strategy = new ConcreteStrategyB();
} else if (strategyType.equals("LOSE")) {
strategy = new ConcreteStrategyA();
}
}
/**
* 调用策略
*/
public void strategyExecute() {
strategy.algorithmLogic();
}
}
public interface Strategy {
public void algorithmLogic();
}
public class ConcreteStrategyA implements Strategy{
@Override
public void algorithmLogic() {
// 具体的算法逻辑(输了比赛)
System.out.println("第一场:上等马vs上等马 第二场:中等马vs中等马 第三场:下等马vs下等马 赛果:输!");
}
}
public class ConcreteStrategyB implements Strategy{
@Override
public void algorithmLogic() {
// 赢
System.out.println("第一场:下等马vs上等马 第二场:上等马vs中等马 第三场:中等马vs下等马 赛果:赢!");
}
}
public class Client {
public static void main(String[] args) {
StrategyFactory strategyFactory = new StrategyFactory();
strategyFactory.build("LOSE");
strategyFactory.strategyExecute();
}
}
-
Примеры паттернов стратегии
-
Алгоритм выполнения котировочного центра
Загрузите шаблон алгоритма из БД, используйте отражение для создания соответствующего объекта алгоритма, а затем отправьте StrategyExecuteContext для выполнения.
сегмент fault.com/ah/119000001…
сегмент fault.com/ah/119000001…
Статус (Мой день)
И шаблон стратегии, и шаблон состояния являются хорошими стратегиями для устранения жестко закодированных конструкций с большим количеством if…else или switch…case.
- Почему был введен шаблон состояния
Назначение паттерна состояния — контролировать ситуацию, когда условное выражение перехода состояния объекта слишком сложно — перевести логику оценки состояния в ряд классов, представляющих разные состояния, что может упростить сложную логику оценки.
- Диаграмма классов
**Шаблон состояния оборачивает поведение изучаемого объекта в различные объекты состояния, каждый из которых принадлежит подклассу абстрактного класса состояния. ** Цель шаблона состояния — позволить объекту изменить свое поведение при изменении его внутреннего состояния. Схематическая диаграмма классов шаблона состояния показана ниже:
- Роль контекста, также известная как контекст: определяет интерфейс, который интересует клиента, и сохраняет экземпляр конкретного класса состояния. Экземпляр этого конкретного класса состояния дает текущее состояние этого объекта среды.
- Роль абстрактного состояния (State): определите интерфейс для инкапсуляции поведения, соответствующего определенному состоянию объекта среды (Context).
- Роль ConcreteState: каждый класс конкретного состояния реализует поведение, соответствующее состоянию среды (контекст).
public class Context {
//持有一个State类型的对象实例
private State state;
public void setState(State state) {
this.state = state;
}
/**
* 用户感兴趣的接口方法
*/
public void request(String sampleParameter) {
//转调state来处理
state.handle(sampleParameter);
}
}
//把研究对象的处理方法放到状态对象中实现
public interface State {
/**
* 状态对应的处理
*/
public void handle(String sampleParameter);
}
public class ConcreteStateA implements State {
@Override
public void handle(String sampleParameter) {
System.out.println("ConcreteStateA handle :" + sampleParameter);
}
}
public class ConcreteStateB implements State {
@Override
public void handle(String sampleParameter) {
System.out.println("ConcreteStateB handle :" + sampleParameter);
}
}
//客户端调用
public class Client {
public static void main(String[] args){
//创建状态
State state = new ConcreteStateB();
//创建环境
Context context = new Context();
//将状态设置到环境中
context.setState(state);
//请求
context.request("test");
}
}
Как видно из вышеизложенного, поведение request() класса среды Context делегируется определенному классу состояния. Используя принцип полиморфизма, содержимое атрибута State класса среды Context может быть динамически изменено с указания на конкретный класс состояния на указание на другой конкретный класс состояния, так что определяется поведение request() класса среды. по различным конкретным состояниям класса для выполнения.
- Пример 2. Реализация конечного автомата
Если для представления текущего перехода состояния используется конечный автомат (FSM), он, вероятно, будет таким:
Для ситуаций, когда состояний не так много и переход не очень сложен, также просто и понятно использовать оценку состояния (метод if else), чтобы справиться с этим. Но как только состояние увеличивается и операция усложняется, бизнес-код будет наводнен различными условными суждениями, и повсюду будет разбросана различная логика обработки состояния. В настоящее время, если вы хотите добавить новое состояние или настроить некоторую логику обработки, это будет более проблематично и подвержено ошибкам.
Например, в этом примере в фактической обработке все еще могут быть такие ситуации, как отмена заказа, сбой / тайм-аут платежа, сбой / тайм-аут возврата и т. Д. Если добавить логистику и некоторые внутренние состояния, обработка будет чрезвычайно сложной, и если вы случайно возникнут ситуации, в которых платеж не будет выполнен, а пользователю все еще может быть возвращена сумма, или возмещение было возвращено и возвращено пользователю для доставки. На самом деле это неприятный запах, который затрудняет поддержку и расширение кода.
Управление конечным автоматом генерации обменного курса.
сегмент fault.com/ah/119000000…
woohoo.cn blog.com/java-no-haircuts…
Наблюдатель (мать сообщает ребенку, чтобы он поел)
- Почему шаблон наблюдателя?
Основным бизнесом является покупка билетов (эта модель не ограничивается этим бизнесом), но вокруг покупки билетов будет генерироваться другая логика, например:
- Запись текстового журнала после покупки билета
- Запись журнала базы данных после покупки билета
- Отправьте СМС после покупки билета
- Покупайте билеты и получайте купоны на скидку, обменные купоны и баллы
- Прочие различные виды деятельности и т.д.
Традиционное решение:
Добавьте соответствующий код внутри класса и т. д., завершите различную логику.
Существует проблема:
- После изменения определенной бизнес-логики, например, добавления другой бизнес-логики в процесс продажи билетов, необходимо изменить основной файл продажи билетов и даже процесс продажи билетов.
- Со временем документы становятся объемными, что затрудняет последующее ведение.
Основной причиной проблемы является "сильная связь" программы. Режим наблюдения используется для оптимизации текущей бизнес-логики до "слабой связи", которую легко поддерживать и модифицировать.
-
Сценарии применения
-
После того, как центр котировок сгенерирует обменный курс для клиентов, ему необходимо выполнить серию посткотировочной обработки.
-
Обработка после покупки
- Диаграмма классов
- Пример - ужин
public class Mother implements Subject{
/**
* 她要通知的孩子。孩子是观察者,妈妈是被观察者。
*/
private ArrayList<Observer> children = new ArrayList<>();
/**
* 通知的内容
*/
private String message;
public Mother(String name) {
super(name);
}
@Override
public void registerObserver(Observer observer) {
children.add(observer);
}
@Override
public void removeObserver(Observer observer) {
children.remove(observer);
}
@Override
public void notifyObserver() {
children.forEach(observer -> observer.message(message));
}
//做事情
public void sendMessage(String message) {
this.message = message;
// 通知她们
notifyObserver();
}
}
public class Child implements Observer{
public Child(String name) {
super(name);
}
@Override
public void message(String m) {
System.out.println(getName() + "收到的消息:" + m);
}
}
public class Main {
public static void main(String[] args) {
Mother mother = new Mother("妈妈");
Child xiaoBing = new Child("小冰");
Child xiaoAi = new Child("小爱");
// 孩子都是亲生的,吃饭时叫她们
mother.registerObserver(xiaoBing);
mother.registerObserver(xiaoAi);
mother.sendMessage("饭煮好了,回来吃饭,买了你们想吃的鸡腿");
System.out.println("------------------分割线-----------------------");
// 小爱说不回来吃了,取消通知她
mother.removeObserver(xiaoAi);
mother.sendMessage("饭煮好了,回来吃饭,买了你们想吃的鸡腿");
}
}
- Разница между шаблоном наблюдателя и шаблоном публикации-подписки
-
существуетнаблюдательВ режиме Субъект знает о наблюдателе, и Субъект ведет учет наблюдателя. Однако вопубликовать подписатьсяшаблон, издатели и подписчикине подозревая о существовании друг друга. Они общаются только через брокера сообщений.
-
существуетопубликовать подписатьсяВ этом шаблоне компоненты слабо связаны, в отличие от шаблона Observer.
-
Шаблон наблюдателябольшую часть времениСинхронизироватьНапример, когда событие инициируется, Субъект вызовет метод наблюдателя. ипубликовать-подписыватьсяЧаще всего режимАсинхронный(используя очередь сообщений).
сегмент fault.com/ah/119000001…
Знакомство с цепочкой ответственности (утверждение возмещения)
- Почему модель цепочки ответственности?
public void test(int i, Request request){
if(i==1){
Handler1.handle(request);
}else if(i == 2){
Handler2.handle(request);
}else if(i == 3){
Handler3.handle(request);
}else if(i == 4){
Handler4.handle(request);
}else{
Handler5.handle(request);
}
}
Бизнес-логика кода следующая.Метод имеет два параметра: целое число i и запрос запроса.По значению i решается кто будет обрабатывать запрос.Если i==1, то он будет обработан Обработчиком 1. Если i==2, обрабатывается Обработчиком 2 и т. д. В программировании этот метод обработки бизнеса очень распространен. Все классы, обрабатывающие запросы, имеют операторы условного суждения if...else..., связанные с цепочкой ответственности за обработку запросов. Я полагаю, что все часто их используют. Преимущество этого метода в том, что он очень интуитивно понятен, прост и удобен в обслуживании, но у этого метода также есть несколько головных болей:
-
**Код раздут:** Условия оценки в практических приложениях обычно не так просты, чтобы судить, является ли это 1 или 2. Это может потребовать сложных вычислений, может потребоваться запросить базу данных и т. д., что требуется много дополнительного кода.Если есть больше условий суждения, то это выражение if...else... в основном нечитаемо.
-
**Высокая степень связанности: **Если мы хотим продолжать добавлять классы, обрабатывающие запросы, то мы должны продолжать добавлять условия else if; кроме того, порядок оценки этого условия также жестко закодирован, если вы хотите изменить порядок, вы можете изменить его только этот условный оператор.
-
Сценарии применения
- Существует несколько объектов, которые могут обрабатывать один и тот же запрос, и какой объект обрабатывает запрос, автоматически определяется средой выполнения.
- Отправьте запрос одному из нескольких объектов без явного указания получателя.
- Набор объектов может быть динамически определен для обработки запросов.
Модуль исполнения стратегии котировочного центра может использовать режим цепочки ответственности для унифицированной передачи StrategyExecuteContext:
- Инициализация конфигурации политики
- Загрузка исходного обменного курса
- Алгоритмическая проверка ограничений
- Выполнение алгоритма
- Диаграмма классов
- Пример — обработка возмещения
//抽象处理类
public abstract class Handler {
protected Handler nextHandler = null;
public Handler getNextHandler() {
return nextHandler;
}
public void setNextHandler(Handler nextHandler) {
this.nextHandler = nextHandler;
}
public abstract String dispose(String user , double fee);
}
//报销处职员,只能报销小于500的金额
public class StaffMember extends Handler {
@Override
public String dispose(String user, double fee) {
if(fee < 500){
System.out.println("报销处职员 给了 "+user+" "+fee+"元");
}else if (super.getNextHandler() == null){
System.out.println("处理不了 "+user+" 要 "+fee+"元的事情");
}else {
super.getNextHandler().dispose(user,fee);
}
return null;
}
}
//处长类
public class SectionChief extends Handler {
@Override
public String dispose(String user, double fee) {
if(fee < 1000){
System.out.println("小主管 给了 "+user+" "+fee+"元");
}else if (super.getNextHandler() == null){
System.out.println("处理不了 "+user+" 要 "+fee+"元的事情");
}else {
super.getNextHandler().dispose(user,fee);
}
return null;
}
}
//老板类
public class Director extends Handler {
@Override
public String dispose(String user, double fee) {
if(fee < 5000){
System.out.println("老大 给了 "+user+" "+fee+"元");
}else if (super.getNextHandler() == null){
System.out.println("处理不了 "+user+" 要 "+fee+"元的事情");
}else {
super.getNextHandler().dispose(user,fee);
}
return null;
}
}
public class Main {
public static void main(String[] args){
StaffMember staffMember = new StaffMember();
SectionChief sectionChief = new SectionChief();
Director director = new Director();
//set Handler
staffMember.setNextHandler(sectionChief);
sectionChief.setNextHandler(director);
staffMember.dispose("小王",400);
staffMember.dispose("小张",800);
staffMember.dispose("小李",1200);
staffMember.dispose("小明",10000);
}
}
В шаблоне «Цепочка ответственности» многие объекты связаны в цепочку посредством ссылки каждого объекта на подчиненный ему объект. Запросы передаются по этой цепочке до тех пор, пока объект в цепочке не решит обработать запрос. Клиент, делающий запрос, не знает, какой объект в цепочке в конечном итоге обрабатывает запрос, что позволяет системе динамически реорганизовывать и распределять обязанности, не затрагивая клиента.
сегмент fault.com/ah/119000000…
Реализовано до весны
Spring настраивает цепочку ответственности за просмотр купонов и внедряет объект цепочки чеков.
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"
default-autowire="byName">
<!-- 优惠券展示链 -->
<bean id="displayCouponCheckChain" class="com.isudox.service.coupon.CheckChain">
<property name="checkList">
<list>
<ref bean="timeCheckHandlerNode"/>
<ref bean="riskCheckHandlerNode"/>
<ref bean="receivedCheckHandlerNode"/>
<ref bean="stockCheckHandlerNode"/>
</list>
</property>
</bean>
<!-- 优惠券发放链 -->
<bean id="sendCouponCheckChain" class="com.isudox.service.coupon.CheckChain">
<property name="checkList">
<list>
<ref bean="receivedCheckHandlerNode"/>
<ref bean="stockCheckHandlerNode"/>
<ref bean="timeCheckHandlerNode"/>
<ref bean="riskCheckHandlerNode"/>
</list>
</property>
</bean>
</beans>
Таким образом можно гибко настроить бизнес купонной части, если нужно добавить логику, можно реализовать интерфейс CheckChainNode без изменения существующего кода. Кроме того, если вы хотите изменить цепочку экранирования, вам нужно только изменить конфигурацию Spring и перезапустить экземпляр, чтобы он вступил в силу, без перекомпиляции и публикации. Просто легкомысленно!
об авторе
Получил степень бакалавра в 2012 году и степень магистра в 2016 году. Он работал в IBM China R&D Center, государственных предприятиях, Ant Financial и многих других компаниях. Он занимается разработкой Java более 10 лет и в настоящее время занимается систематическим обобщением и обменом знаниями, связанными с архитектурой распределенных приложений. Надеюсь, он будет полезен тем, кому он нужен для систематического изучения и накопления смежных областей.