菜鸟成长系列拖了一周多了,今天继续之前的思路来进行。按照之前的规划,这篇主要来学习设计原则先关知识。通过本文学习,希望大家一方面能是能够认识这些原则是什么,能够在日常的开发中起到怎样的约束,并且用这些原则来提高代码的复用性和可维护性,另一方面是对后续的设计模式的学习能够有一些基础。
Серия Rookie Growth — обзор
Rookie Growth Series - Четыре основных особенности объектно-ориентированного подхода
Rookie Growth Series — полиморфизм, интерфейсы и абстрактные классы
Принципы проектирования, упомянутые в книге Java and Patterns, используются для улучшения ремонтопригодности системы и, в то же время, улучшения возможности повторного использования системы. В этой книге основное внимание уделяется шести принципам дизайна:
- Принцип «открыто-закрыто».
- Принцип замены Лисков
- Принцип инверсии зависимости
- Принцип разделения интерфейса
- Принцип единой ответственности
- Закон Дитми
Эти принципы проектирования в первую очередь являются принципами повторного использования.Следование этим принципам может эффективно повысить возможность повторного использования системы, а также улучшить ремонтопригодность системы.
Принцип «открыто-закрыто».
Я видел объяснение человека в Интернете, он сравнил это так: книга написана, нельзя оторвать несколько бумажек и наклеить на нее, лучше всего купить новую.
Причина в том, что программа, которая уже была завершена, не поддерживает модификацию, потому что в случае модификации это может привести к сбою программы или сообщению об ошибке.Поэтому обычно программа поддерживает только расширение и не поддерживает модификацию.
- 1. Почему существует такой принцип, как ограничение на разработку программы?
在软件的生命周期内,由于软件功能或者结构的变化、升级和维护等原因需要对软件原有代码进行修改,在修改的过程中可能会给旧代码中引入错误,也可能会使我们不得不对整个功能进行重构,并且还需要进行软件的重新测试,因此我们希望在软件设计之初,能够用一种原则来进行一些基本的约束,使得在软件后期的功能变更、扩展或者维护更加容易
- 2. Какую проблему решает принцип открытого-закрытого?
当软件需要进行改变时,我们应该尽量通过扩展软件实体的行为来实现变化,而不是通过修改已有的代码来实现变化。通过这样一种原则,可以很好的实现在保证原有功能稳定的前提下扩展新的功能
- 3. Что такое принцип открытого-закрытого?
一个软件实体(类、模块或函数)应当对扩展开放,对修改关闭。也就是说在扩展或者修改软件功能时,应尽量在不修改原有代码的情况下进行
Чтобы дать простой каштан: Сейчас есть такой спрос, система должна проверить и войти через QQ. Хорошо, давайте запустим код:
- Пользователь класса пользователя
package com.glmapper.framerwork;
/**
* 用户信息类
* @author glmapper
* @date 2017年12月9日下午10:54:09
*
*/
public class User {
private String userName;//用户名
private String passWord;//密码
public String getUserName() {
return userName;
}
public void setUserName(String userName) {
this.userName = userName;
}
public String getPassWord() {
return passWord;
}
public void setPassWord(String passWord) {
this.passWord = passWord;
}
}
- Основная логика проверки QQ
package com.glmapper.framerwork;
/**
* QQ验证器
* @author glmapper
* @date 2017年12月9日下午10:49:24
*/
public class QQAuther {
/**
* 用于验证QQ登录信息
*/
public boolean validateQQ(User user)
{
//模拟下逻辑
return user.toString()==null?false:true;
}
}
- Базовый класс службы аутентификации
package com.glmapper.framerwork;
/**
*
* 用于验证的核心服务
* @author glmapper
* @date 2017年12月9日下午10:47:04
*
*/
public class AuthService {
//持有一个QQ验证器对象
private QQAuther qqAuther;
//通过构造器注入qqAuther对象
public AuthService(QQAuther qqAuther) {
this.qqAuther = qqAuther;
}
/*
* 验证用户合法性
*/
public boolean validateUser(User user){
return qqAuther.validateQQ(user);
}
}
- клиент
package com.glmapper.framerwork;
/**
* 客户端调用验证
* @author glmapper
* @date 2017年12月9日下午10:50:13
*
*/
public class AuthClient {
public static void main(String[] args) {
//获取用户信息
User user = UserHolder.getUser();
QQAuther qqAuther = new QQAuther();
AuthService authService = new AuthService(qqAuther);
//获取验证结果
boolean isOK = authService.validateUser(user);
System.out.println(isOK);
}
}
Хорошо, уйди! Но теперь нам нужно получить доступ к интерфейсу открытого платформы Microblogging; изменить код .... Добавьте валидатор микроблогов:
package com.glmapper.framerwork;
/**
* 微博核心验证器
* @author glmapper
* @date 2017年12月9日下午11:01:10
*/
public class WeiBoAuther {
/**
* 用于验证QQ登录信息
*/
public boolean validateWeiBo(User user)
{
return user.toString()==null?false:true;
}
}
Модификация основной службы аутентификации:
package com.glmapper.framerwork;
/**
*
* 用于验证的核心服务
* @author glmapper
* @date 2017年12月9日下午10:47:04
*
*/
public class AuthService {
//持有一个QQ验证器对象
private Object obj;
//通过构造器注入qqAuther对象
public AuthService(Object obj) {
this.obj = obj;
}
/*
* 验证用户合法性
*/
public boolean validateUser(User user){
//这里仅作为模拟,一般情况下会通过使用定义枚举&工厂模式来完成
if (obj instanceof QQAuther) {
return new QQAuther().validateQQ(user);
}
if(obj instanceof WeiBoAuther){
return new WeiBoAuther().validateWeiBo(user);
}
return false;
}
}
Изменения клиента:
package com.glmapper.framerwork;
/**
* 客户端调用验证
* @author glmapper
* @date 2017年12月9日下午10:50:13
*
*/
public class AuthClient {
public static void main(String[] args) {
//获取用户信息
User user = UserHolder.getUser();
//QQ
QQAuther qqAuther = new QQAuther();
boolean isQQOK = new AuthService(qqAuther).validateUser(user);
System.out.println(isQQOK);
//微博
WeiBoAuther weiBoAuther = new WeiBoAuther();
boolean isWeiBoOK = new AuthService(weiBoAuther).validateUser(user);
System.out.println(isWeiBoOK);
}
}
ОК, улучшение сделано! Но есть новые требования, доступ к WeChat.... Если сейчас у нас есть доступ к открытой платформе WeChat, то нам нужно получить доступ к учетным записям Alipay, учетным записям Suning Tesco и так далее. . . Вам нужно постоянно модифицировать код. Затем в это время нам нужно использовать наш открытый и закрытый принцип, чтобы сделать ограничение в начале дизайна. Продолжайте катить:
Сначала нам нужно определить интерфейс для ограничений:
- Интерфейс валидатора, который раньше реализовывался валидаторами QQ/WEIBO/WeChat/Suning Tesco и другими платформами разработки.
package com.glmapper.framerwork;
/**
* 定义一个约束接口
* @author glmapper
* @date 2017年12月9日下午11:32:32
*
*/
public interface ValidateInteface {
/**
* 提供一个验证入口
*/
boolean validate(User user);
}
- После изменения QQ
package com.glmapper.framerwork;
/**
* QQ验证器
* @author glmapper
* @date 2017年12月9日下午10:49:24
*/
public class QQAuther implements ValidateInteface{
/**
* 用于验证QQ登录信息
*/
@Override
public boolean validate(User user) {
return user.toString()==null?false:true;
}
}
- Weibo после модификации
package com.glmapper.framerwork;
/**
* 微博核心验证器
* @author glmapper
* @date 2017年12月9日下午11:01:10
*/
public class WeiBoAuther implements ValidateInteface{
/**
* 用于验证QQ登录信息
*/
@Override
public boolean validate(User user) {
// TODO Auto-generated method stub
return user.toString()==null?false:true;
}
}
- Основная служба аутентификации
package com.glmapper.framerwork;
/**
* 用于验证的核心服务
* @author glmapper
* @date 2017年12月9日下午10:47:04
*/
public class AuthService {
//持有一个QQ验证器对象
private ValidateInteface validate;
//通过构造器注入qqAuther对象
public AuthService(ValidateInteface validate) {
this.validate = validate;
}
/*
* 验证用户合法性
*/
public boolean validateUser(User user){
return validate.validate(user);
}
}
- клиент
package com.glmapper.framerwork;
/**
* 客户端调用验证
* @author glmapper
* @date 2017年12月9日下午10:50:13
*
*/
public class AuthClient {
public static void main(String[] args) {
//获取用户信息
User user = UserHolder.getUser();
//QQ
ValidateInteface qqAuther = new QQAuther();
boolean isQQOK = new AuthService(qqAuther).validateUser(user);
System.out.println(isQQOK);
//微博
ValidateInteface weiBoAuther = new WeiBoAuther();
boolean isWeiBoOK = new AuthService(weiBoAuther).validateUser(user);
System.out.println(isWeiBoOK);
}
}
После улучшения мы можем обнаружить, что для исходного основного класса службы аутентификации и каждого класса валидатора, независимо от того, каким способом добавить доступ, нам не нужно изменять его код. На данный момент все, что нам нужно сделать, это добавить валидатор (например, валидатор Suning Tesco), а затем унаследовать интерфейс ValidateInterface. В общем, ядром принципа открытого-закрытого является:
- Аннотация
- Принцип инкапсуляции вариативности (1. Неизменяемость не должна быть разбросана по множеству мест кода, а должна быть инкапсулирована в объекте; 2. Один вид изменчивости не должен смешиваться с другим видом изменчивости)
(Если у вас есть более простой пример насилия, вы можете оставить сообщение; этот пример не очень уместен после долгих раздумий, и он все еще абстрагирован от работы).
Принцип замены Лисков
任何父类可以出现的地方,子类一定可以出现
Принцип подстановки Лисков является дополнением к принципу "открытие и закрытие". Как было сказано выше, ключевым шагом в реализации принципа "открытие и закрытие" является абстракция, а отношение наследования между родительскими классами и подклассами является конкретным проявлением абстракции Итак, принцип замещения Лискова — это спецификация конкретных шагов для достижения абстракции.
摘自java与模式中的定义:如果对每一个类型为 T1的对象 o1,都有类型为 T2 的对象o2,使得以 T1定义的所有程序 P 在所有的对象 o1 都代换成 o2 时,程序 P 的行为没有发生变化,那么类型 T2 是类型 T1 的子类型。
На приведенной ниже диаграмме показаны отношения наследования, от животных самого высокого уровня до конкретных животных. ОК, напишите код останова, чтобы увидеть:
- Абстрактный родительский класс верхнего уровня — животное
package com.glmapper.framework.model.lsp;
/**
* 顶层抽象父类动物类
* @author glmapper
* @date 2017年12月10日上午10:51:30
*/
public abstract class Animal {
//提供一个抽象方法,以供不同子类来进行具体的实现
public abstract void eatFood(String foodName);
}
- Конкретный тип животного — собака
package com.glmapper.framework.model.lsp;
/**
*子类-小狗
* @author glmapper
* @date 2017年12月10日上午10:54:17
*
*/
public class Dog extends Animal{
@Override
public void eatFood(String foodName) {
System.out.println("小狗吃"+foodName);
}
}
- Бетонное животное - хаски
package com.glmapper.framework.model.lsp;
/**
* 具体小狗的种类-子类哈士奇
* @author glmapper
* @date 2017年12月10日上午10:56:59
*
*/
public class HSQDog extends Dog{
/**
* 重写父类方法
*/
@Override
public void eatFood(String foodName) {
System.out.println("哈士奇吃"+foodName);
}
}
- клиент
package com.glmapper.framework.model.lsp;
//客户端程序
public class ClientMain {
public static void main(String[] args) {
//子类
HSQDog hsqdog=new HSQDog();
hsqdog.eatFood("饼干");
//父类
Dog dog = new HSQDog();
dog.eatFood("饼干");
//顶层父类
Animal animal = new HSQDog();
animal.eatFood("饼干");
}
}
- результат операции
哈士奇吃饼干
哈士奇吃饼干
哈士奇吃饼干
Видно, что наши слова мы начали.Везде, где может появиться родительский класс, должен появиться дочерний класс., обратное неверно. Насколько я понимаю, подкласс получает атрибуты и поведение родительского класса через интеграцию, а сам подкласс также имеет свои собственные атрибуты и поведение; местом, где может появиться родительский класс, должна быть необходимость использования атрибутов или поведения родительского класса. родительский класс, а подкласс имеет свои собственные атрибуты и поведение.Все они охватывают эту информацию родительского класса, поэтому ее можно заменить. Обратное невозможно, потому что родительский класс действует только как ограничение типа в приведенном выше примере, он может не иметь некоторых характеристик дочернего класса, поэтому его нельзя по-настоящему заменить.
里氏替换原则是继承复用的基石,只有当子类可以替换掉基类,软件单位的功能不会受到影响时,基类才能被真正的复用,而子类也才能够在基类的基础上增加新的功能。
Принцип инверсии зависимости
实现“开闭”原则的关键是抽象化,并且从抽象化导出具体化实现。如果说开闭原则是面向对象设计的目标的话,依赖倒转原则就是面向对象设计的主要机制(java与模式)。
依赖倒转原则:要依赖与抽象,不依赖于具体实现。
Как понять?
-
1) Модуль высокого уровня не должен напрямую зависеть от конкретной реализации базового модуля, но должен зависеть от абстракции базового модуля. Другими словами, зависимость между модулями должна происходить посредством абстракции, а между классами нет прямой зависимости, которая порождается интерфейсом или абстрактным классом.
-
2) Интерфейсы и абстрактные классы не должны зависеть от реализующих классов, а реализующие классы зависят от интерфейсов или абстрактных классов. Излишне говорить, что этот момент хорошо понят.Идея «интерфейсно-ориентированного программирования» является лучшим воплощением этого.
Во-первых, это первый момент: с точки зрения повторного использования высокоуровневые модули должны повторно использоваться разработчиками. Но в традиционном процедурном дизайне повторное использование сосредоточено на повторном использовании модулей определенного уровня. Например, повторное использование алгоритмов, повторное использование структур данных, повторное использование библиотек функций и т. д. неизбежно повторно используются в модулях определенного уровня. Структуры более высокого уровня зависят от структур более низкого уровня, которые, в свою очередь, зависят от структур более низкого уровня, пока каждая строка кода не станет зависимой. Затем низкоуровневые модификации также будут модифицироваться слой за слоем, вплоть до модуля проектирования верхнего уровня.
对于一个系统来说,一般抽象层次越高,它的稳定性就越好,因此也是作为复用的重点
.
"Инверсия" на самом деле означает, что повторное использование должно быть сосредоточено на абстрактном уровне. Если модули на абстрактном уровне относительно независимы от модулей на конкретном уровне, то повторное использование модулей на абстрактном уровне относительно просто.
Во многих случаях программа на Java должна ссылаться на объект.Если объект имеет абстрактный тип, абстрактный тип следует использовать в качестве статического типа переменной. Выше мы нарисовали диаграмму классов отношений между животными и собаками.Существует три способа, когда клиент звонит://子类(方式1)
HSQDog hsqdog=new HSQDog();
hsqdog.eatFood("饼干");
//父类(方式2)
Dog dog = new HSQDog();
dog.eatFood("饼干");
//顶层父类(方式3)
Animal animal = new HSQDog();
animal.eatFood("饼干");
Если нам нужен хаски (HSQDog), мы не должны использовать режим 1, а должны использовать режим 2 или режим 3.
Принцип разделения интерфейса
接口隔离原则:使用多个专门的接口比使用单一的总接口要好。换句话说,从一个客户类的角度来讲:一个类对另外一个类的依赖性应当是建立在最小的接口上的。
На самом деле, это часто встречается в нашем реальном развитии. Например, нам нужно написать несколько операционных интерфейсов для завершения продукта.
package com.glmapper.framework.model.isp;
/**
* 一个产品服务接口
* @author glmapper
* @date 2017年12月10日下午12:01:31
*/
public interface ProductService {
//增加产品
public int addProduct(Product p);
//删除产产品
public int deleteProduct(int pId);
//修改产品
public int updateProduct(Product p);
//查询一个产品
public Product queryProduct(int pId);
}
Хорошо, мы предоставляем продукты для добавления и удаления, чтобы изменить поиск в ProductService, но по мере необходимости обновления мы можем увеличить потребность в новых массовых импортных и экспортных продуктах. ОК, затем продолжайте добавлять два метода в интерфейс:
//从excel中批量导入
public void batchImportFromExcel();
//从excel中批量导导出
public void batchExportFromExcel();
Затем спрос должен быть расширен, и необходимо увеличить закупку продуктов, производство заказов на продукты, запрос заказов и детализацию заказов... Таким образом, наша служба продуктов будет медленно и быстро расширяться. . Соответствующая конкретная логика реализации класса ProductServiceImpl также станет очень большой, и один класс может превышать тысячи строк кода.
Затем нам нужно изолировать интерфейс, поместить основные операции с продуктом, такие как добавление, удаление, модификация и проверка, в один интерфейс, поместить обработку заказа на продукт в один интерфейс, поместить покупку продукта в один интерфейс, поместить пакетные операции в один интерфейс, и т. д. Для каждого интерфейса нас интересует только определенный тип конкретной ответственности, который на самом деле немного связан с принципом единой ответственности.通过这种设计,降低了单个接口的复杂度,使得接口的“内聚性”更高,“耦合性”更低。由此可以看出接口隔离原则的必要性。
Закон Дитми
迪特米法则:又称为最少知识原则,就是说一个对象应当对其他对象尽可能少的了解;看下迪特米法则的几种表述:
1.只与你直接的朋友们通信
2.不跟陌生人说话
3.每一个软件单位对其他的单位都只有最少知识,而且局限于那些与本单位密切相关的软件单位
也就是说,如果两个雷不必彼此直接通信,那么这两个类就不应当发生直接的相互作用。如果其中一个类需要电泳另一个类的某一个方法的话,可以通过第三者进行消息的转发。 Посмотрите на код:
- кто то
package com.glmapper.framework.model.isp;
/**
* 某个人
* @author glmapper
* @date 2017年12月10日下午12:39:45
*/
public class SomeOne {
//具体oprateion行为
public void oprateion(Friend friend){
Stranger stranger =friend.provide();
stranger.oprateion3();
}
}
SomeOne具有一个oprateion方法,该方法接受Friend为参数,根据上面的定义可以知道Friend是SomeOne的“朋友”(直接通信了)
- друг
package com.glmapper.framework.model.isp;
/**
* 朋友
* @author glmapper
* @date 2017年12月10日下午12:40:09
*/
public class Friend {
private Stranger stranger = new Stranger();
public Stranger provide(){
return stranger;
}
public void opration2(){
}
}
很明显SomeOne的opration方法不满足迪特米法则,因为这个方法中涉及到了陌生人Stranger,Stranger不是SomeOne的朋友
Хорошо, давайте преобразуем по закону Дитми.
- Кто-то после ремонта
package com.glmapper.framework.model.isp;
/**
* 某个人
* @author glmapper
* @date 2017年12月10日下午12:39:45
*
*/
public class SomeOne {
//具体oprateion行为
public void oprateion(Friend friend){
friend.forward();
}
}
- друзья после ремонта
package com.glmapper.framework.model.isp;
/**
* 朋友
* @author glmapper
* @date 2017年12月10日下午12:40:09
*
*/
public class Friend {
private Stranger stranger = new Stranger();
public void opration2(){
}
//进行转发
public void forward() {
stranger.oprateion3();
}
}
Поскольку вызывается переадресация, прямая связь в SomeOne с незнакомцем Stranger игнорируется. Соблюдены условия для прямого общения с друзьями и не разговоров с незнакомцами.
Но проблема, вызванная законом Дитми, также очевидна: то есть в системе будет создано большое количество мелких методов, разбросанных по всем углам системы, эти методы являются лишь вызовами для передачи сообщений и не имеют никакого отношения к делу. логика системы.
единственная ответственность
В вышеупомянутой интерфейсной изоляции мы упомянули, одна ответственность на самом деле очень хорошее понимание, объясняет как можно больше, чтобы каждый из наших классов или завершить работу в интерфейсе только в одной другой логике. Например, яблочный сок, который я только что сжал яблочный сок, если вам нужно сжать сок огурца, то вам нужно купить соковыжималку огурца.
Суммировать
Хорошо, здесь принцип дизайна завершен. в заключении:
-
- Принцип единой ответственности требует, чтобы класс реализации имел единую ответственность;
-
- Принцип замещения Лисков требует не нарушать систему наследования;
-
- Принцип инверсии зависимостей требует интерфейсно-ориентированного программирования;
-
- Принцип изоляции интерфейса требует, чтобы интерфейс был упрощенным и единым;
-
- Закон Деметры требует, чтобы связь была уменьшена;
-
- Принцип открытого-закрытого является общей схемой, требующей разработки расширений и закрытия модификаций.
Всем отличных выходных! (Если есть что-то неуместное, надеюсь, вы вовремя укажете на это, спасибо!)