Статьи из серии "Шаблоны проектирования"
Кровавая рвота, самый полный одноэлементный режим во всей сети
Режим адаптера универсального адаптера
предисловие
В прошлой статье говорилось о рефлексии как о базовых знаниях фреймворка. Перейдите к порталу, если вы его еще не видели,Отражение резюме 4D (Душа кадра). Сегодня мы рассмотрим шаблоны проектирования. Без лишних слов, поехали.
Что такое шаблоны проектирования?
Шаблоны проектирования предназначены для разработки программного обеспечения.普遍存在的问题
, предлагаемое решение.
Это не имеет ничего общего с самим проектом, будь то электронная коммерция, ERP, OA и т. д., шаблоны проектирования могут использоваться для решения сопутствующих задач.
Конечно, если это программное обеспечение используется только небольшим количеством людей, а функции очень просты, никаких серьезных модификаций и дополнений в обозримом будущем вноситься не будет, то есть шаблон проектирования можно опустить. Но их слишком мало, поэтому шаблоны проектирования по-прежнему очень важны.
Зачем использовать шаблоны проектирования?
Конечная цель использования шаблонов проектирования — «высокая связность и низкая связанность».
- Повторное использование кода: код с одной и той же функцией, написанный не так много раз.
- Удобочитаемость кода: нормативный для программирования, легко читаемый другими программистами
- Расширяемость кода: добавление новых функций не влияет на исходные функции.
Семь принципов шаблонов проектирования
Существует 7 основных принципов шаблонов проектирования, а именно, то есть это не только основа шаблонов проектирования, но и принципы, которых мы должны придерживаться в привычном нам программировании.
1. Принцип единой ответственности
Зная название, мы пытаемся спроектировать класс, отвечающий за одну функцию, например, класс А отвечает только за функцию А, а класс В отвечает только за функцию Б. Не позволяйте классу А отвечать за обе функции А и функция B, что приведет к путанице в коде, склонной к ошибкам.
Неиспользование принципа единой ответственности
Один класс:
public class single {
public static void main(String[] args) {
Vehicle vehicle = new Vehicle();
vehicle.run("汽车");
vehicle.run("轮船");
vehicle.run("飞机");
}
}
Класс автомобиля:
public class Vehicle {
void run(String type){
System.out.println(type+"在公路上开");
}
}
результат операции:
Посмотрим на беговые результаты, машина едет по дороге, а корабль и самолет не по дороге. Этот дизайн проблематичен, потому что класс Vehicle отвечает более чем за одну функцию.
Используется принцип единой ответственности
Для приведенного выше примера мы используем принцип единой ответственности, чтобы переписать его и разделить класс Транспортное средство на три класса, а именно Автомобиль, Корабль и Самолет, и позволить им нести ответственность за транспортные средства на суше, воде и воздухе, так что они независимы друг от друга.
Если нам нужно ограничить водное движение до «уровень ветра больше 8, выход в море запрещен», нам нужно только изменить класс корабля.
Конкретный код выглядит следующим образом:
один класс:
public class single {
public static void main(String[] args) {
Car car = new Car();
car.run("汽车");
Ship ship=new Ship();
ship.run("轮船");
Plane plane=new Plane();
plane.run("飞机");
}
}
Класс автомобиля:
public class Car {
void run(String type){
System.out.println(type+"在公路上开");
}
}
Класс корабля:
public class Ship {
void run(String type){
System.out.println(type+"在水里开");
}
}
Класс самолета:
public class Plane {
void run(String type){
System.out.println(type+"在天空开");
}
}
результат операции:
оптимизация
Мы можем обнаружить, что принцип единой ответственности — это слишком много кода и он кажется излишним. В конце концов, мы, программисты, можем писать меньше, когда можем писать меньше, и мы никогда не должны писать больше кода. Затем мы его оптимизируем, у каждого из вышеперечисленных классов есть только один метод, мы можем объединить его в один класс, есть три метода, каждый метод соответствует транспортному средству на дороге, на воде, в небе, принцип единой ответственности Падая на уровень метода, а не на уровень класса, код выглядит следующим образом:
один класс:
public class single {
public static void main(String[] args) {
Vehicle vehicle = new Vehicle();
vehicle.runOnRoad("汽车");
vehicle.runOnWater("轮船");
vehicle.runOnAir("飞机");
}
}
Класс автомобиля:
public class Vehicle {
void runOnRoad(String type){
System.out.println(type+"在公路上开");
}
void runOnWater(String type){
System.out.println(type+"在水里开");
}
void runOnAir(String type){
System.out.println(type+"在天空开");
}
}
результат операции:
Резюме преимуществ и недостатков
преимущество:
- Уменьшите сложность класса, класс отвечает только за одну ответственность.
- Улучшите читаемость кода, и логика будет понятна.
- Чтобы снизить риск, изменяйте только один класс, не затрагивая функциональность других классов.
2. Принцип изоляции интерфейса
Классы не должны полагаться на интерфейсы, которые им не нужны, а интерфейсы должны быть максимально разбиты на мелкие частицы.
Принцип разделения интерфейсов не используется
Класс людей:
public interface People {
void exam();
void teach();
}
Студенческий класс:
public class Student implements People {
@Override
public void exam() {
System.out.println("学生考试");
}
@Override
public void teach() {
}
}
Класс учителя:
public class Teacher implements People{
@Override
public void exam() {
}
@Override
public void teach() {
System.out.println("教师教书");
}
}
тестовый класс:
public class test {
public static void main(String[] args){
People student=new Student();
student.exam();
People teacher=new Teacher();
teacher.teach();
}
}
результат операции:
注:此处代码并没有报错,正常运行的,但是看得代码冗余且奇怪。Student只需要实现People的exam方法,而Teacher只需要实现People的teach方法,但是现在Student实现了People接口,就必须重写exam和teach方法,Teacher也是如此。
Используемый принцип разделения интерфейса
Мы разделяем два метода интерфейса People на два интерфейса People1 и People2 и позволяем Sudent реализовать интерфейс People1, а Teacher реализовать интерфейс People2, чтобы они не мешали друг другу Конкретный код выглядит следующим образом:
Люди1 класс:
public interface People1 {
void exam();
}
Класс Люди2:
public interface People2 {
void teach();
}
Студенческий класс:
public class Student implements People1 {
@Override
public void exam() {
System.out.println("学生考试");
}
}
Класс учителя:
public class Teacher implements People2 {
@Override
public void teach() {
System.out.println("教师教书");
}
}
тестовый класс:
public class test {
public static void main(String[] args){
People1 student=new Student();
student.exam();
People2 teacher=new Teacher();
teacher.teach();
}
}
результат операции:
Суммировать
Ближе к делу, если вы объединяете несколько методов в интерфейс и предоставляете его другим системам для использования, вы должны реализовать все методы интерфейса, а некоторые методы вообще не нужны, что вызывает путаницу у пользователей.
3. Принцип инверсии зависимостей
Модули высокого уровня не должны зависеть от модулей низкого уровня, оба должны зависеть от интерфейсов или абстрактных классов.
Его ядром является интерфейсно-ориентированное программирование.
Принцип инверсии зависимостей в основном основан на следующих концепциях проектирования: по сравнению с изменчивостью деталей абстрактные вещи гораздо более стабильны, а архитектура, основанная на абстракции, более стабильна, чем архитектура, основанная на деталях.
Abstract относится к интерфейсу или абстрактному классу, а детали относятся к конкретному классу реализации.
Слишком сухо так говорить, копируя Сюаньке, без души, сказать, значит не сказать. Далее проиллюстрируем на примере.
Не использовать принцип инверсии зависимостей
Поскольку сейчас особый период, давайте сначала возьмем пример покупки продуктов. Ниже приведен пример глупой белой сладости, в которой не используется принцип инверсии зависимостей.
Цинцай класс:
public class Qingcai {
public void run(){
System.out.println("买到了青菜");
}
}
Класс людей:
public class People {
public void bug(Qingcai qingcai){
qingcai.run();
}
}
тестовый класс:
public class test {
public static void main(String[] args){
People people=new People();
people.bug(new Qingcai());
}
}
результат операции:
Задавайте вопросы, меняйте идеи (акцент)
Вышеупомянутое выглядит хорошо, но что, если он не хочет покупать зеленые овощи и хочет купить редис? Конечно, мы можем создать новую редьку и дать ей метод run, но проблема в том, что у People нет метода для манипулирования редисом, а еще нам нужно добавить в People зависимость от редиса. Таким образом, объем кода, который нужно изменить, слишком велик, а связь между модулями слишком высока.Пока требуется небольшое изменение, требуется большая область реконструкции, поэтому дизайн неразумен.Давайте взгляните на его диаграмму классов, как показано ниже:
Этот дизайн является способом мышления в общем дизайне, а инверсия в Принципе инверсии зависимостей относится к полной противоположности обычного образа мышления, начиная снизу, то есть начиная с Цинцай и Луобо, а затем задаваясь вопросом, если что-то можно абстрагироваться. Очевидно, что все они овощи, а затем мы вернемся и переосмыслим, как спроектировать, новый дизайн выглядит следующим образом:
(Пожалуйста, простите мою инвалидность, я плохо рисую...)
Мы видим, что низкоуровневые классы абстрагируются от интерфейса Shucai, который напрямую взаимодействует с высокоуровневыми классами, в то время как некоторые низкоуровневые классы не участвуют, что может уменьшить связанность кода и повысить стабильность.
Был использован принцип инверсии зависимостей.
Когда у вас появится идея, давайте поиграем с кодом.
Шукай класс:
public interface Shucai {
public void run();
}
Цинцай класс:
public class Qingcai implements Shucai{
public void run(){
System.out.println("买到了青菜");
}
}
Луобо класс:
public class Luobo implements Shucai {
@Override
public void run() {
System.out.println("买到了萝卜");
}
}
Класс людей:
public class People {
public void bug(Shucai shucai){
shucai.run();
}
}
тестовый класс:
public class test {
public static void main(String[] args){
People people=new People();
people.bug(new Qingcai());
people.bug(new Luobo());
}
}
результат операции:
Суммировать
Этот принцип фокусируется на «реверсировании», мышлении с нижнего уровня и максимальном абстрагировании абстрактных классов и интерфейсов. Этот пример является хорошим объяснением того, что «модули верхнего уровня не должны зависеть от модулей нижнего уровня, все они должны зависеть от абстракций». В первоначальном проекте модуль верхнего уровня зависит от модуля нижнего уровня.После настройки и модуль верхнего уровня, и модуль нижнего уровня зависят от интерфейса Shucai, и отношения зависимости «инвертируются», насколько это возможно. видно из рисунка.
4. Принцип подстановки Лисков.
Преимущества и недостатки наследования
Принцип замещения Лисков был предложен женщиной по фамилии Ли из Массачусетского технологического института в 1988 году, в которой излагаются некоторые взгляды на расширение наследования.
Преимущества наследования:
- Чтобы улучшить возможность повторного использования кода, подклассы также имеют свойства и методы родительского класса.
- Для улучшения масштабируемости кода у подклассов есть свои уникальные методы.
Недостатки наследства:
Когда родительский класс изменяется, рассмотрите возможность модификации подкласса.
В основе наследования лежит принцип замещения Лискова.Только когда подкласс заменяет родительский класс, программные функции все еще не затрагиваются, и родительский класс действительно используется повторно.
Использование принципа подстановки Лисков 1
Подклассы должны реализовывать абстрактные методы суперкласса, но не должны переопределять (переопределять) неабстрактные (реализованные) методы суперкласса.
反例
Родительский класс А:
public class A {
public void run(){
System.out.println("父类执行");
}
}
Подкласс Б:
public class B extends A{
public void run(){
System.out.println("子类执行");
}
}
Тест тестового класса:
public class test {
public static void main(String[] args) {
A a = new A();
a.run();
System.out.println("将子类替换成父类:");
B b = new B();
b.run();
}
}
результат операции:
注:我每次使用子类替换父类的时候,还要担心这个子类有没有可能导致问题。此处子类不能直接替换成父类,故没有遵循里氏替换原则。
Использование принципа подстановки Лискова 2
Подклассы могут добавлять свои собственные уникальные методы
Родительский класс А:
public class A {
public void run(){
System.out.println("父类执行");
}
}
Подкласс Б:
public class B extends A{
public void runOwn(){
System.out.println("子类执行");
}
}
Тест тестового класса:
public class test {
public static void main(String[] args) {
A a = new A();
a.run();
System.out.println("将子类替换成父类:");
B b = new B();
b.run();
b.runOwn();
}
}
результат операции:
注:父类A 有run方法,继承父类A的子类B有runOwn方法,测试类test先是调用A类的run方法,接着用B类替换A类,发现还是执行的是父类A的run方法,最后再调用子类B特有的方法runOwn方法。如上,说明该段代码已使用了里氏替换原则。
Использование принципа подстановки Лискова 3
Когда подкласс переопределяет или реализует метод суперкласса, предварительные условия метода (то есть формальные параметры метода) более смягчены, чем входные параметры метода суперкласса.
Родительский класс А:
public class A {
public void run(HashMap hashMap){
System.out.println("父类执行");
}
}
Подкласс Б:
public class B extends A{
public void run(Map map){
System.out.println("子类执行");
}
}
Тест тестового класса:
public class test {
public static void main(String[] args) {
A a = new A();
a.run(new HashMap());
System.out.println("将子类替换成父类:");
B b = new B();
b.run(new HashMap());
}
}
результат операции:
Мы видим, что в тестовом классе test, когда родительский класс A заменяется дочерним классом B, результат выполнения «выполнение родительского класса» все еще отображается Мы можем обнаружить, что это не переопределение, а перегрузка метода, потому что параметр не тот, так что он на самом деле нормализация наследования, чтобы лучше использовать наследование. Относительно того, является ли это перегрузкой метода или переписыванием, мы можем видеть из следующего рисунка:
Если его перезаписать, то в красной позиции значка выше появится стрелка, и мы увидим, что он на самом деле перегружен.
Что делать, если это правило не используется? См. код ниже:
Родительский класс А:
public class A {
public void run(Map map){
System.out.println("父类执行");
}
}
Подкласс Б:
public class B extends A{
public void run(HashMap hashMap){
System.out.println("子类执行");
}
}
тестовый тест:
public class test {
public static void main(String[] args) {
A a = new A();
a.run(new HashMap());
System.out.println("将子类替换成父类:");
B b = new B();
b.run(new HashMap());
}
}
результат операции:
Мы можем видеть, что когда область действия подкласса больше, чем у родительского класса, замененный подкласс по-прежнему выполняет свой собственный метод подкласса. Это не соответствует принципу замещения Лискова.
Суммировать
Обычно мы не следуем этим принципам замены Лискова, и программа по-прежнему работает нормально. На самом деле, если вы не будете следовать принципу подстановки Лискова, шансы того, что ваш код пойдет не так, значительно возрастут.
5. Принцип открытия и закрытия (акцент)
основное введение
Можно сказать, что первые четыре принципа, принцип единой ответственности, принцип экранирования интерфейса, принцип инверсии зависимостей и принцип подстановки Лисков являются основой принципа открытого-закрытого, который является наиболее основным и важным принципом проектирования программирования. резюме, а ядром является разработка расширений. Близко к модификации, проще говоря, изменение достигается за счет расширения поведения программного обеспечения, а не путем модификации, старайтесь не изменять код, а расширять код.
Принцип открыто-закрыто не используется
Интерфейсный транспорт:
public interface transport {
public void run();
}
Bus:
public class Bus implements transport {
@Override
public void run() {
System.out.println("大巴在公路上跑");
}
}
Когда мы изменим требования, чтобы автобус также мог двигаться по воде, мы можем добавить метод в класс Bus. Но это нарушило принцип открытого-закрытого.Если бизнес сложный, такие модификации чреваты проблемами.
Используется открытый закрытый принцип
Мы можем добавить новый класс, реализовать транспортный интерфейс, наследовать класс Bus и написать свои собственные потребности.
public class universalBus extends Bus implements transport {
@Override
public void run() {
System.out.println("大巴既然在公路上开,又能在水里开");
}
}
6. Принцип Деметры
вводить
- Объект должен иметь минимальные знания о других объектах.
- Чем ближе класс к классу, тем больше степень связанности
- Чем меньше класс знает о классах, от которых он зависит, тем лучше. То есть, независимо от сложности зависимого класса, постарайтесь инкапсулировать логику внутри класса. За исключением предоставленных общедоступных методов, никакая информация не просачивается во внешний мир.
- Есть более простое определение Закона Деметры: общаться только с непосредственными (знакомыми) друзьями
- Прямые (знакомые) друзья: каждый объект будет иметь отношения связи с другими объектами.Пока существует связь связи между двумя объектами, мы говорим, что эти два объекта являются друзьями. Существует много способов соединения, зависимости, ассоциации, композиции, агрегации и т. д.
Среди них мы называем классы, которые появляются в переменных-членах, параметрах метода и возвращаемых значениях метода, прямыми друзьями, в то время как классы, появляющиеся в локальных переменных, не являются прямыми друзьями. То есть незнакомые классы лучше не появляться внутри класса в виде локальных переменных.
Перевод вышеприведенных понятий одно за другим человеческими словами:
- Наша одноклассница, из-за того, что мы слишком замкнуты и не умеем общаться, мы не очень хорошо знакомы с другими одноклассниками.
- Девушка на самом деле слишком застенчива.После того, как вы скажете другим еще несколько слов, вы будете нервничать и подавлены, и будете часто делать ошибки.
- Сдержанная классная девочка, хотя ее ум очень активен, любит больше думать. Но для других это чувство чисто, как чистый лист бумаги.
- Потому что девушка слишком замкнута, боится незнакомцев и считает незнакомцев плохими людьми, поэтому общается только со знакомыми друзьями.
- Знакомые друзья класса girl: переменные-члены, параметры метода и объекты возвращаемых значений метода. А классы, которые появляются в других местах, — незнакомцы, плохие парни! Эта девушка отказывается с тобой общаться! ! !
Ха-ха, это должны понимать все. Все в одном предложении:一个类应该尽量不要知道其他类太多的东西,不要和陌生的类有太多接触
.
Неиспользование принципа Деметры
Класс сотрудников головного офиса:
public class Employee {
private String id;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
}
Класс SubEmployee сотрудника филиала:
public class SubEmployee {
private String id;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
}
Класс EmployeeManager управления сотрудниками головного офиса:
public class EmployeeManager {
public List<Employee> setValue(){
List<Employee> employees=new ArrayList<Employee>();
for(int i=0;i<10;i++){
Employee employee=new Employee();
employee.setId("总公司"+i);
employees.add(employee);
}
return employees;
}
public void printAllEmployee(SubEmployeeManager sub){
List<SubEmployee> list1 = sub.setValue();
for(SubEmployee e:list1){
System.out.println(e.getId());
}
List<Employee> list2 = this.setValue();
for(Employee e:list2){
System.out.println(e.getId());
}
}
}
Класс SubEmployeeManager управления сотрудниками филиала:
public class SubEmployeeManager {
public List<SubEmployee> setValue(){
List<SubEmployee> subEmployees=new ArrayList<SubEmployee>();
for(int i=0;i<10;i++){
SubEmployee subEmployee=new SubEmployee();
subEmployee.setId("分公司"+i);
subEmployees.add(subEmployee);
}
return subEmployees;
}
}
Тестовый класс:
public class test {
public static void main(String[] args){
EmployeeManager employeeManager=new EmployeeManager();
SubEmployeeManager subEmployeeManager=new SubEmployeeManager();
employeeManager.printAllEmployee(subEmployeeManager);
}
}
результат операции:
Приведенный выше код работает нормально, но вы видите проблему: локальная переменная SubEmployee, используемая в методе printAllEmployee класса EmployeeManager, не соответствует закону Деметры, она является чужой и должна отказаться от общения.
Был использован принцип Деметры.
Класс менеджера сотрудников:
public class EmployeeManager {
public List<Employee> setValue() {
List<Employee> employees = new ArrayList<Employee>();
for (int i = 0; i < 10; i++) {
Employee employee = new Employee();
employee.setId("总公司" + i);
employees.add(employee);
}
return employees;
}
public void printAllEmployee(SubEmployeeManager sub) {
sub.printAllSubEmployee();
List<Employee> list2 = this.setValue();
for (Employee e : list2) {
System.out.println(e.getId());
}
}
}
Класс SubEmployeeManager:
public class SubEmployeeManager {
public List<SubEmployee> setValue(){
List<SubEmployee> subEmployees=new ArrayList<SubEmployee>();
for(int i=0;i<10;i++){
SubEmployee subEmployee=new SubEmployee();
subEmployee.setId("分公司"+i);
subEmployees.add(subEmployee);
}
return subEmployees;
}
public void printAllSubEmployee(){
List<SubEmployee> list1 = setValue();
for(SubEmployee e:list1){
System.out.println(e.getId());
}
}
}
Мы перенесли код печати ветки в методе printAllEmployee класса EmployeeManager в класс SubEmployeeManager класса управления веткой, а затем отобразили в методе метод вызова класса SubEmployeeManager, что соответствует Закону Деметры.
7. Принцип синтеза и повторного использования
Попробуйте использовать композицию/коллекцию вместо наследования.
Если вы используете наследование, это усилит связь, попробуйте использовать его как входной параметр метода или переменную-член класса, чтобы избежать связи.
Эпилог
Все принципы — это просто спецификации, чтобы сделать код более элегантным, сделать его понятным с первого взгляда. Если принцип не должен соблюдаться, код все еще может работать, но вероятность ошибок в будущем возрастет.
Вышеизложенное, вкратце, в основном включает в себя два пункта:
1. Узнайте, что нужно изменить в приложении самостоятельно, не смешивайте с исправленным.
2. Программирование, ориентированное на интерфейс, а не на программирование, ориентированное на реализацию.
просить внимания
Оригинальность не так проста, пожалуйста, обратите больше внимания, маленькая девочка ждет вас.
использованная литература
Шесть принципов шаблонов проектирования (1): принцип единой ответственности
Семь принципов шаблонов проектирования (1) -- принцип единой ответственности
Принцип инверсии зависимостей (DIP) из шести принципов проектирования
Принцип замещения Лисков в шаблонах проектирования