Какие болевые точки призваны решить шаблоны проектирования?
Это серия подпрограмм для решения конкретных задач, обобщение опыта разработки кода предшественников, она универсальна и может использоваться многократно. его цель состоит в том, чтобы
提高代码的可复用性、可读性、可维护性
.
Суть шаблона проектирования заключается в практическом применении принципов объектно-ориентированного проектирования, а также в полном понимании инкапсуляции, наследования и полиморфизма классов, а также отношений ассоциации и композиции классов.
不要重复造轮子
.
Что такое объектно-ориентированное программирование
-
面向对象编程
Парадигма программирования или стиль программирования. Он берет классы или объекты в качестве базовой единицы для организации кода и использует четыре характеристики инкапсуляции, абстракции, наследования и полиморфизма в качестве краеугольного камня проектирования и реализации кода. -
面向对象编程语言
Это язык программирования, который поддерживает синтаксический механизм классов или объектов и имеет готовый синтаксический механизм, который может легко реализовать четыре характеристики объектно-ориентированного программирования. -
面向对象开发
В том числе объектно-ориентированный анализ ООП, объектно-ориентированное проектирование ООД, объектно-ориентированное программирование ООП.
10 принципов дизайна
1. Принцип единой ответственности SRP
实现类要职责单一
: Если блок кода (модуль функционального класса) отвечает за несколько функций, то изменение кода при изменении требований функции A может вызвать проблемы с функцией B, поэтому блок кода должен отвечать только за одну ответственность.
2. OCP по принципу «открыто-закрыто»
要对扩展开放,对修改关闭
: Реализация новых функций путем модификации старого кода может привести к ошибкам в старых модулях, поэтому мы должны реализовывать новые функции, разрабатывая новые блоки кода.
3. Принцип подстановки Лисков LSP
不要破坏继承体系
: подклассы в программе должны иметь возможность заменять родительский класс везде, где он встречается, и оставаться в соответствии с ожиданиями. Таким образом, подкласс старается не изменять ожидаемое поведение метода суперкласса.
4. Принцип изоляции интерфейса ISP
设计接口的时候要精简单一
: Когда класс A является только частично требуемым интерфейсным методом B, поскольку весь его способ внедрения интерфейса необходимо реализовать, что вызывает, что вызывает несколько сегментов кода, не нужно. В этом случае интерфейс должен быть разделен B, класс нужный и нежелательный способ изолировать.
5. Принцип инверсии зависимостей DIP
面向接口编程
: Абстракция не должна зависеть от деталей, детали должны зависеть от абстракции. Ядром является интерфейсно-ориентированное программирование, и мы должны полагаться на абстрактные интерфейсы, а не на конкретные классы реализации интерфейса или конкретные объекты.
Примечание. SOLID выше также известен как 5 принципов дизайна.
6. Принцип наименьшего знания (принцип Деметры) LOD
降低耦合度
: класс или объект должны иметь минимальные знания о других объектах. Общайтесь только с ближайшими друзьями (сцепление).
7. Принцип комбинационного/агрегационного мультиплексирования CRP
多用组合少用继承
: Всегда, когда это возможно, реализуйте новые функциональные возможности путем составления существующих объектов (заимствуя их возможности), а не используя наследование для приобретения этих возможностей.
8. Не повторяйтесь СУХО
Функциональное семантическое дублирование должно быть объединено, дублирование выполнения кода должно быть сокращено,代码逻辑重复但语义不同应该保留
.
9. ДЕРЖИТЕ ЭТО ПРОСТО, ПОЦЕЛУЙТЕ
Используйте простой и читаемый код для максимально возможной реализации функций вместо сложной логики, сложной для реализации и плохой читабельности.
10. Не переусердствуйте с логикой, которая вам временно не нужна YAGNI
Не переоптимизируйте, не оставляйте слишком много точек расширения и не создавайте код, который ваши коллеги не смогут понять.
Как оценить качество кода?
- Читаемость, масштабируемость, ремонтопригодность, возможность повторного использования, тестируемость...
- Высокая сплоченность и низкая связанность.
善战者无赫赫之功善医者无煌煌之名,大智若愚大巧若拙,真正的好代码并不是用了多少厉害的技术与奇技淫巧,而是看尽人世繁华后的返璞归真,寥寥几笔实现了功能的同时却没有任何个人风格的痕迹,符合代码规范、编程思想、设计模式的代码。
Как сформировать долговременную память?
-
想办法把零散的知识点串联起来记忆
- Память пирамидальной структуры формируется сверху вниз.
- Составьте ключевые фразы для запоминания.
-
得意忘形
- Извлеките суть знаний, чтобы укрепить память, удалите грубое и извлеките суть.
-
学而不思则罔,思而不学则殆
- Глубокое мышление действительно может преобразовать знания других в свои собственные.
-
学而时习之,不亦说乎
- Первое обучение — это всего лишь кратковременная память в уме,
需要多次复习强化才能形成长期记忆。
- Первое обучение — это всего лишь кратковременная память в уме,
Меры предосторожности
Знания мертвы, а код жив. Не используйте застывшие шаблоны проектирования для реализации запрограммированной бизнес-логики.
Наша цель обучения состоит в том, чтобы иметь возможность применять то, что мы узнали, но если код, написанный другими людьми в той же группе, не может быть понят, это повлияет на удобство сопровождения и эффективность разработки проекта. Так что мы можем использовать его экономно, но мы должны овладеть его мышлением.
Твердо усвойте режим дизайна, берите интервью, берите интервью у других в группе, чтобы поделиться или может отпугнуть стаю.
23 шаблона проектирования.
- В сокращении: 5, 7, 11, 23 — все нечетные числа.
- 5 видов творчества
- 7 типов структур
- 11 типов поведения
- Тип создания:
抽工单建原型
- Абстрактная фабрика, фабрика, один случай, строитель, прототип
- Структурный тип:
桥代理装饰适配器,享元组合成门面
- мост, прокси, декоратор, переходник, привес, композиция, фасад (фасад)
- Поведенческий тип:
观察模板迭代的状态,命令中介解释职责链,访问策略备忘录
- Наблюдатель, Шаблон, Итерация, Состояние, Команда, Посредник, Интерпретатор, Цепочка ответственности, Посетитель, Политика, Памятка
Создание шаблонов проектирования
封装对象创建过程,将对象的创建和使用解耦
одноэлементный шаблон
Сценарии применения
Обрабатывает нарушения доступа к ресурсам и используется для создания глобально уникальных классов.
решение
- Ленивый: создавайте его, когда вам это нужно (сценарий: не обязательно нужен, дорого создавать, ленивая загрузка, нужно быстро запустить систему).
- Голодный китайский стиль: создается при запуске системы (сценарий: необходимо использовать, временное создание влияет на скорость отклика).
- Несколько экземпляров: Фиксированное количество одинаковых экземпляров одного и того же класса.
Заводская выкройка
Сценарии применения
используется для создания继承同一父类、实现同一接口的子类
Объект, который создает конкретный объект с заданными параметрами типа.
решение
enum HelloType {
A,
B
}
interface Hello {
sayHello()
}
class A implements Hello {
sayHello() {
console.log('A');
}
}
class B implements Hello {
sayHello() {
console.log('B');
}
}
class HelloFactory {
static list = new Map<HelloType, Hello>([
[HelloType.A, new A()],
[HelloType.B, new B()]
])
static getHello(type: HelloType) {
return HelloFactory.list.get(type)
}
}
// test
HelloFactory.getHello(HelloType.A).sayHello()
HelloFactory.getHello(HelloType.B).sayHello()
Абстрактный заводской узор
Сценарии применения
继承同一父类、实现同一接口的子类
Object, который создает конкретный объект из заданных нескольких параметров типа.
решение
enum Type {
A,
B
}
enum Occupation {
TEACHER,
STUDENT
}
interface Hello {
sayHello()
}
class TA implements Hello {
sayHello() {
console.log('Teacher A say hello')
}
}
class TB implements Hello {
sayHello() {
console.log('Teacher B say hello')
}
}
class SA implements Hello {
sayHello() {
console.log('Student A say hello')
}
}
class SB implements Hello {
sayHello() {
console.log('Student B say hello')
}
}
class AFactory {
static list = new Map<Occupation, Hello>([
[Occupation.TEACHER, new TA()],
[Occupation.STUDENT, new SA()]
])
static getHello(occupation: Occupation) {
return AFactory.list.get(occupation)
}
}
class BFactory {
static list = new Map<Occupation, Hello>([
[Occupation.TEACHER, new TB()],
[Occupation.STUDENT, new SB()]
])
static getHello(occupation: Occupation) {
return BFactory.list.get(occupation)
}
}
class HelloFactory {
static list = new Map<Type, AFactory | BFactory>([
[Type.A, AFactory],
[Type.B, BFactory]
])
static getType(type: Type) {
return HelloFactory.list.get(type)
}
}
// test
HelloFactory.getType(Type.A).getHello(Occupation.TEACHER).sayHello()
HelloFactory.getType(Type.A).getHello(Occupation.STUDENT).sayHello()
HelloFactory.getType(Type.B).getHello(Occupation.TEACHER).sayHello()
HelloFactory.getType(Type.B).getHello(Occupation.STUDENT).sayHello()
режим строителя
Сценарии применения
- Есть много обязательных параметров, которые необходимо проверить при создании.
- Во время создания оценка параметров является последовательной и взаимозависимой.
- Существует много шагов для создания, все из которых являются успешными для создания объекта.
решение
class Programmer {
age: number
username: string
color: string
area: string
constructor(p) {
this.age = p.age
this.username = p.username
this.color = p.color
this.area = p.area
}
toString() {
console.log(this)
}
}
class Builder {
age: number
username: string
color: string
area: string
build() {
if (this.age && this.username && this.color && this.area) {
return new Programmer(this)
} else {
throw new Error('缺少信息')
}
}
setAge(age: number) {
if (age > 18 && age < 36) {
this.age = age
return this
} else {
throw new Error('年龄不合适')
}
}
setUsername(username: string) {
if (username !== '小明') {
this.username = username
return this
} else {
throw new Error('小明不合适')
}
}
setColor(color: string) {
if (color !== 'yellow') {
this.color = color
return this
} else {
throw new Error('yellow不合适')
}
}
setArea(area: string) {
this.area = area
return this
}
}
// test
const p = new Builder()
.setAge(20)
.setUsername('小红')
.setColor('red')
.setArea('hz')
.build()
.toString()
режим прототипа
Сценарии применения
- Шаблон прототипа клонирует данные на основе существующего объекта, а не изменяет цепочку прототипов!
- Стоимость создания объекта слишком высока, а значения атрибутов разных объектов-экземпляров одного типа в основном одинаковы. Экономьте ресурсы, клонируя прототипы.
- Неизменяемые объекты реализуются поверхностным клонированием.
- Изменяемые объекты реализуются посредством глубокого клонирования, которое требует много ресурсов.
- Различные версии одного и того же объекта в разное время могут сравнить неизмененный поверхностный клон и измененный глубокий клон, а затем заменить старую версию новой версией.
Шаблоны структурного проектирования
总结了一些类或对象组合在一起的经典结构,这些经典结构可以解决特定应用场景的问题,将类或对象的结构和使用解耦
режим моста
Сценарии применения
- Разделите абстракцию и реализацию, чтобы они могли меняться независимо.
- Класс имеет несколько измерений, которые изменяются независимо друг от друга, и мы позволяем независимо расширять несколько измерений путем их объединения.
- Очень похоже на принцип композиции по наследованию.
решение
enum MsgLevel {
ERROR,
WARN,
}
enum MsgType {
EMAIL,
PHONE
}
interface MsgContent {
content()
}
class ErrorMsg implements MsgContent {
content() {
return 'ERROR'
}
}
class WarnMsg implements MsgContent {
content() {
return 'WARN'
}
}
interface MsgSender {
send()
}
class PhoneSend implements MsgSender {
msgContent: MsgContent
constructor(msgContent: MsgContent) {
this.msgContent = msgContent
}
send() {
console.log(`phone send ${this.msgContent.content()}`)
}
}
class EmailSend implements MsgSender {
msgContent: MsgContent
constructor(msgContent: MsgContent) {
this.msgContent = msgContent
}
send() {
console.log(`email send ${this.msgContent.content()}`)
}
}
// test 此处还可以做成map结构继续优化(略)
new PhoneSend(new WarnMsg()).send()
new PhoneSend(new ErrorMsg()).send()
new EmailSend(new WarnMsg()).send()
new EmailSend(new ErrorMsg()).send()
прокси-режим
Сценарии применения
- Добавьте нефункциональные требования к исходному классу, чтобы отделить код от исходного бизнеса.
- Разработка нефункциональных требований к бизнес-системам: мониторинг, статистика, аутентификация, ограничение тока, логирование, кеширование.
решение
- Реализовано через наследование (не рекомендуется)
class User{
login(){
console.log('user login...')
}
}
class UserProxy extends User{
login() {
console.log('login before')
super.login()
console.log('login after')
}
}
- Реализовано через интерфейс (рекомендуется)
interface Login {
login()
}
class User implements Login {
login() {
console.log('user login...')
}
}
class UserProxy implements Login {
user = new User()
login() {
console.log('login before')
this.user.login()
console.log('login after')
}
}
Шаблон декоратора
Сценарии применения
- Класс декоратора - это улучшение оригинальной функции.
- Класс декоратора наследует тот же родительский класс, что и исходный класс, поэтому мы можем вкладывать несколько классов декоратора в исходный класс.
- В основном это решает проблему, заключающуюся в том, что отношения наследования слишком сложны, и заменяет наследование через композицию.
- Можно использовать несколько декораторов, вложив исходный класс.
решение
- Реализовано с помощью АОП
Function.prototype.before = function (beforeFn) {
return (...arg) => {
beforeFn(...arg);
return this(...arg);
}
};
Function.prototype.after = function (afterFn) {
return (...arg) => {
const result = this(...arg);
afterFn(...arg);
return result;
}
};
function ImportEvent1 {
console.log('重要的事情说三遍 1')
}
function ImportEvent2 {
console.log('重要的事情说三遍 2')
}
function ImportEvent3 {
console.log('重要的事情说三遍 3')
}
// test
ImportEvent2.before(ImportEvent1).after(ImportEvent3)()
режим адаптера
Сценарии применения
- Шаблон адаптера используется для устранения недостатков дизайна и обеспечения совместимости несовместимых интерфейсов.
- Инкапсулируйте ошибочный дизайн интерфейса.
- Унифицируйте дизайн интерфейса нескольких классов.
- Замените зависимые внешние системы.
- Совместим со старыми версиями интерфейса.
- Адаптируйтесь к данным в разных форматах.
решение
- В исходном интерфейсе не так много методов, можно использовать как адаптеры классов, так и адаптеры объектов.
- Если исходный класс имеет много методов и разница с целевым интерфейсом невелика, используйте адаптер класса, чтобы уменьшить объем кода.
- Если в исходном классе много методов и разница с целевым интерфейсом велика, используйте объектный адаптер, а композиция лучше, чем наследование.
// 目标接口格式
interface ITarget {
f1()
f2()
f3()
}
// 原有类与目标接口不兼容
class Origin {
fa() {
}
fb() {
}
f3() {
}
}
// 使用适配器来兼容
class Adaptor implements ITarget {
origin = new Origin()
f1() {
this.origin.fa()
}
f2() {
this.origin.fb()
}
f3() {
this.origin.f3()
}
}
наилегчайший образец
Сценарии применения
- общий блок. Повторное использование объектов для экономии памяти при условии, что объект-легковес является неизменяемым объектом (он не изменится после инициализации).
решение
- Например, в онлайн-шахматной игре 1000 комнат, в каждой комнате есть шахматная доска, текущее состояние шахматной доски (положение фигур) разное, но размер, цвет и название фигур одинаковые и фиксированные, и может быть спроектирован как Flyweight.
Комбинированный режим
Сценарии применения
Организуйте набор объектов в древовидную структуру, чтобы представить иерархию часть-целое. Режим композиции позволяет клиентам унифицировать логику обработки (рекурсивный обход) отдельных объектов и составных объектов.
решение
abstract class FileSystemNode {
path: string
abstract getFilesCount()
abstract getFilesSize()
}
class FileNode extends FileSystemNode {
constructor(path) {
super();
this.path = path
}
getFilesCount() {
return 1
}
getFilesSize() {
return require(this.path).length
}
}
class Directory extends FileSystemNode {
subNodes = []
constructor(path) {
super();
this.path = path
}
getFilesCount() {
return this.subNodes.reduce(item => item.getCount(), 0)
}
getFilesSize() {
return this.subNodes.reduce(item => item.getSize(), 0)
}
}
Режим фасада (внешнего вида)
Сценарии применения
- Объедините несколько запросов внутреннего интерфейса в один (избыточный интерфейс), чтобы повысить скорость ответа и решить проблемы с производительностью.
- За счет инкапсуляции мелких интерфейсов и предоставления высокоуровневых интерфейсов, сочетающих различные мелкие интерфейсы, упрощается использование интерфейсов.
Шаблоны поведенческого проектирования
总结了一些类或对象交互的经典方式,将该行为相关的类或对象解耦
Шаблон наблюдателя
Сценарии применения
- Отделите наблюдателя от наблюдаемого.
- В режиме публикации-подписки есть центр планирования публикации-подписки (посредник), а в режиме наблюдателя его нет!
решение
// 目标对象
class Subject {
observerList: Observer[]
constructor() {
this.observerList = [];
}
addObserver(observer) {
this.observerList.push(observer);
}
notify() {
this.observerList.forEach((observer) => {
observer.update();
});
}
}
// 观察者
class Observer {
cb: Function
constructor(cb: Function) {
if (typeof cb === "function") {
this.cb = cb;
} else {
throw new Error("Observer构造器必须传入函数类型!");
}
}
update() {
this.cb();
}
}
// test
const observerCallback = function () {
console.log("我被通知了");
};
const observer = new Observer(observerCallback);
const subject = new Subject();
subject.addObserver(observer);
subject.notify();
Режим шаблона
Сценарии применения
- Определите скелет алгоритма (бизнес-логики) в методе и отложите определенные шаги до подклассов. Шаблон Template Method позволяет подклассам переопределять определенные шаги в алгоритме без изменения общей структуры алгоритма.
- Повторное использование расширения.
решение
abstract class Drinks {
firstStep() {
console.log('烧开水')
}
abstract secondStep()
thirdStep() {
console.log('倒入杯子')
}
abstract fourthStep()
drink() {
this.firstStep()
this.secondStep()
this.thirdStep()
this.fourthStep()
}
}
class Tea extends Drinks {
secondStep() {
console.log('浸泡茶叶')
}
fourthStep() {
console.log('加柠檬')
}
}
class Coffee extends Drinks {
secondStep() {
console.log('冲泡咖啡')
}
fourthStep() {
console.log('加糖')
}
}
// test
const tea = new Tea()
tea.drink()
const coffee = new Coffee()
coffee.drink()
режим стратегии
Сценарии применения
- Определите семейство алгоритмов, инкапсулируйте каждый алгоритм отдельно и сделайте их взаимозаменяемыми.
- Избегайте многословных операторов if-else или переключений.
решение
enum StrategyType {
S,
A,
B
}
const strategyFn = {
'S': function (salary: number) {
return salary * 4
},
'A': function (salary: number) {
return salary * 3
},
'B': function (salary: number) {
return salary * 2
}
}
const calculateBonus = function (level: StrategyType, salary: number) {
return strategyFn[level](salary)
}
calculateBonus(StrategyType.A, 10000) // 30000
Схема цепочки ответственности
Сценарии применения
- Несколько процессоров ABC обрабатывают один и тот же запрос по очереди, чтобы сформировать цепочку Когда процессор может обработать запрос, он не будет передаваться последующим процессорам.
- Процессор-перехватчик фильтров.
решение
const order500 = function (orderType, pay, stock) {
if (orderType === 1 && pay === true) {
console.log("500 元定金预购, 得到 100 元优惠券");
return true;
} else {
return false;
}
};
const order200 = function (orderType, pay, stock) {
if (orderType === 2 && pay === true) {
console.log("200 元定金预购, 得到 50 元优惠券");
return true;
} else {
return false;
}
};
const orderCommon = function (orderType, pay, stock) {
if ((orderType === 3 || !pay) && stock > 0) {
console.log("普通购买, 无优惠券");
return true;
} else {
console.log("库存不够, 无法购买");
return false;
}
};
class chain {
fn: Function
nextFn: Function
constructor(fn: Function) {
this.fn = fn;
this.nextFn = null;
}
setNext(nextFn) {
this.nextFn = nextFn
}
init(...arguments) {
const result = this.fn(...arguments);
if (!result && this.nextFn) {
this.nextFn.init(...arguments);
}
}
}
const order500New = new chain(order500);
const order200New = new chain(order200);
const orderCommonNew = new chain(orderCommon);
order500New.setNext(order200New);
order200New.setNext(orderCommonNew);
order500New.init(3, true, 500); // 普通购买, 无优惠券
режим состояния
Сценарии применения
- Каждое состояние внутри объекта инкапсулировано в класс, и изменения во внутреннем состоянии приводят к различному поведению.
решение
class weakLight {
light: Light
constructor(light: Light) {
this.light = light
}
press() {
console.log('打开强光')
this.light.setState(this.light.strongLight)
}
}
class strongLight {
light: Light
constructor(light: Light) {
this.light = light
}
press() {
console.log('关灯')
this.light.setState(this.light.offLight)
}
}
class offLight {
light: Light
constructor(light: Light) {
this.light = light
}
press() {
console.log('打开弱光')
this.light.setState(this.light.weakLight)
}
}
class Light {
weakLight: weakLight
strongLight: strongLight
offLight: offLight
currentState: offLight | weakLight | strongLight //当前状态: 默认关灯状态
constructor() {
this.weakLight = new weakLight(this)
this.strongLight = new strongLight(this)
this.offLight = new offLight(this)
this.currentState = this.offLight
}
press() {
this.currentState.press()
}
setState(state) {
this.currentState = state
}
}
// test
const light = new Light()
light.press()
light.press()
light.press()
light.press()
light.press()
light.press()
шаблон итератора
Сценарии применения
- Итерация по объектам коллекции.
шаблон посетителя
Сценарии применения
- Позволяет применять одну или несколько операций к набору объектов, отделяя операции от самих объектов.
режим заметки
Сценарии применения
- Не нарушая принципа инкапсуляции, захватывать внутреннее состояние объекта и сохранять это состояние вне объекта, чтобы впоследствии объект можно было восстановить в прежнее состояние.
решение
class Programmer {
age: number
username: string
color: string
area: string
constructor(p) {
this.age = p.age
this.username = p.username
this.color = p.color
this.area = p.area
}
// 创建一个快照
createSnapshot() {
return {
age: this.age,
username: this.username,
color: this.color,
area: this.area
}
}
// 通过快照恢复对象状态
restoreSnapshot(snapshot: Programmer) {
this.age = snapshot.age
this.username = snapshot.username
this.color = snapshot.color
this.area = snapshot.area
}
}
командный режим
Сценарии применения
- Основная функция и сценарий применения командного режима — управление выполнением команд, таких как асинхронные, отложенные, очереди, команды отмены и повтора, команды сохранения и команды регистрации. Разделите инициатора и исполнителя команды.
решение
interface Command {
execute()
}
class closeDoorCommand implements Command {
execute() {
console.log('close door');
}
}
class openPcCommand implements Command {
execute() {
console.log('open pc');
}
}
class openQQCommand implements Command {
execute() {
console.log('login qq');
}
}
class CommandManager {
commandList: Command[] = []
addCommand(command: Command) {
this.commandList.push(command)
}
execute() {
this.commandList.forEach(command => {
command.execute()
})
}
}
//test
const commandManager = new CommandManager();
commandManager.addCommand(new closeDoorCommand());
commandManager.addCommand(new openPcCommand());
commandManager.addCommand(new openQQCommand());
commandManager.execute();
Режим переводчика
Сценарии применения
Для данного языка определите его грамматическое представление и определите интерпретатор, который использует эту идентичность для интерпретации предложений на языке.
модель посредничества
Сценарии применения
- Идея дизайна шаблона посредничества очень похожа на идею среднего уровня.Путем введения среднего уровня посредничества отношение взаимодействия (отношение зависимости) между группой объектов преобразуется в отношение «один ко многим» ( звездные отношения). Первоначально объект должен взаимодействовать с n объектами, но теперь ему нужно взаимодействовать только с одним промежуточным объектом, что сводит к минимуму взаимодействие между объектами, снижает сложность кода и улучшает читаемость и удобство сопровождения кода.
别忘记对我素质三连,点赞、关注、评论