-
Автор темы
- #1
Перед прочтением основного контента ниже, пожалуйста, обратите внимание на обновление внутри секции Майна на нашем форуме. У нас появились:
- бесплатные читы для Майнкрафт — любое использование на свой страх и риск;
- маркетплейс Майнкрафт — абсолютно любая коммерция, связанная с игрой, за исключением продажи читов (аккаунты, предоставления услуг, поиск кодеров читов и так далее);
- приватные читы для Minecraft — в этом разделе только платные хаки для игры, покупайте группу "Продавец" и выставляйте на продажу свой софт;
- обсуждения и гайды — всё тот же раздел с вопросами, но теперь модернизированный: поиск нужных хаков, пати с игроками-читерами и другая полезная информация.
Спасибо!
Сегодня хотелось бы поговорить о принципах SOLID или о том, как из пастера начать эволюционировать в человека.
SOLID - это принципы разработки, следуя которым вы получите хороший код, который в дальнейшем будет хорошо масштабироваться и поддерживаться в рабочем состоянии.
Рассмотрим первый принцип - Single Responsibility
Допустим у нас есть класс PcShopService и в нем есть несколько методов: найти пк по номеру, заказать пк, распечатать заказ, получить информацию о пк, отправить сообщение.
У данного класса есть несколько зон ответственности, что является нарушением первого принципа. Возьмем метод получения информации о пк. Теперь у нас есть только три типа пк firstPc, secondPc и thirdPc, но если человек захочет добавить еще несколько типов, тогда придется изменять и дописывать данный метод.
Или возьмем метод отправки сообщения. Если кроме отправки сообщения по электронной почте необходимо будет добавить отправку смс, то также необходимо будет изменять данный метод.
Одним словом, данный класс нарушает принцип единой ответственности, так как отвечает за разные действия.
Необходимо разделить данный класс PcShopService на несколько, и тем самым, следуя принципу единой ответственности, предоставить каждому классу отвечать только за одну зону или действие, так в дальнейшем его будет проще дополнять и модифицировать.
Необходимо создать класс PrinterService и вынести там функционал по печати.
Аналогично работа связанная с поиском информации о пк должна быть перенесена в класс PcInfoService.
Метод по отправке сообщений перенести в класс NotificationService.
А метод поиска пк в PcService.
И в классе PcShopService останется только один метод.
Теперь каждый класс несет ответственность только за одну зону и есть только одна причина для его изменения.
Принцип открытости-закрытости (Open Closed) рассмотрим на примере только что созданного класса по отправке сообщений.
Допустим нам необходимо кроме отправки сообщения по электронной почте отправлять еще смс сообщения. И мы можем дописать метод sendMessage таким образом:
Но в данном случае мы нарушим второй принцип, потому что класс должен быть закрыт для модификации, но открыт для расширения, а мы модифицируем (изменяем) метод.
Для того чтобы придерживаться принципа открытости-закрытости нам необходимо спроектировать наш код таким образом, чтобы каждый мог повторно использовать нашу функцию, просто расширив ее. Поэтому создадим интерфейс NotificationService и в нем поместим метод sendMessage.
Далее создадим класс EmailNotification, который имплементит интерфейс NotificationService и реализует метод отправки сообщений по почте.
Создадим аналогично класс MobileNotification, который будет отвечать за отправку сообщений на телефон.
Проектируя таким образом код мы не будем нарушать принцип открытости-закрытости, так как мы расширяем нашу функциональность, а не изменяем наш класс.
Третий принцип: Liskov Substitution
Данный принцип связан с наследованием классов. Допустим у нас есть базовый класс Account, в котором есть три метода: просмотр остатка на счете, пополнение счета и оплата.
Нам необходимо написать еще два класса: зарплатный счет и депозитный счет, при этом зарплатный счет должен поддерживать все операции, представленные в базовом классе, а депозитный счет - не должен поддерживать проведение оплаты.
Если сейчас в коде программы везде, где мы использовали класс Account заменить на его подтип SalaryAccount, то код продолжит нормально работать, так как в классе SalaryAccount доступны все операции, которые есть и в классе Account.
Для того чтобы следовать принципу необходимо в родительский класс выносить только общую логику, характерную для классов наследников, которые будут ее реализовывать и, соответственно, можно будет базовый класс без проблем заменить на его класс-наследник.
Мы сможем от него наследовать класс DepositAccount.
Создадим дополнительный класс PaymentAccount, который унаследуем от Account и его расширим методом проведения оплаты.
И наш класс SalaryAccount уже унаследуем от класса PaymentAccount.
Принцип подстановки Лисков заключается в правильном использовании отношения наследования. Мы должны создавать наследников какого-либо базового класса тогда, когда они собираются правильно реализовать его логику, не вызывая проблем при замене родителей на наследников.
Принцип разделения интерфейсов (Interface Segregation)
Допустим у нас есть интерфейс Payments и в нем есть три метода: оплата WebMoney, оплата банковской картой и оплата по номеру.
Далее нам надо реализовать два класса-сервиса, которые будут у себя реализовывать различные виды проведения оплат (класс InternetPaymentService и TerminalPaymentService). При этом TerminalPaymentService не будет поддерживать проведение оплат по номеру телефона. Но если мы оба класса имплементим от интерфейса Payments, то мы будем заставлять TerminalPaymentService реализовывать метод, который ему не нужен.
Таким образом произойдет нарушение принципа разделения интерфейсов.
Для того чтобы этого не происходило необходимо разделить наш исходный интерфейс Payments на несколько и, создавая классы, имплементить в них только те интерфейсы с методами, которые им нужны.
Рассмотрим последний принцип: принцип инверсии зависимостей (Dependency Inversion)
Допустим мы пишем приложение для магазина и решаем вопросы с проведением оплат. Вначале это просто небольшой магазин, где оплата происходит только за наличные. Создаем класс Cash и класс Shop.
Вроде все хорошо, но мы уже нарушили принцип инверсии зависимостей, так как мы тесно связали оплату наличными к нашему магазину. И если в дальнейшем нам необходимо будет добавить оплату еще банковской картой и телефоном (100% понадобится), то нам придется переписывать и изменять много кода.
Поэтому создадим интерфейс Payments.
Теперь все наши классы по оплате будут имплементить данный интерфейс.
Теперь надо перепроектировать наш магазин.
Сейчас наш магазин слабо связан с системой оплаты, то есть он зависит от абстракции и уже не важно каким способом оплаты будут пользоваться - все будет работать.
Мы рассмотрели на примерах псевдокода все принципы SOLID, надеюсь кому-то будет это полезно.
SOLID - это принципы разработки, следуя которым вы получите хороший код, который в дальнейшем будет хорошо масштабироваться и поддерживаться в рабочем состоянии.
Плавно перейдём от слов к делу.S - Single Responsibility (принцип единственной ответственности)
Каждый класс должен иметь только одну зону ответственности.
O - Open Closed (принцип открытости-закрытости)
Классы должны быть открыты для расширения, но закрыты для изменения.
L - Liskov Substitution (принцип подстановки Лисков)
Должна быть возможность вместо базового (родительского) типа (класса) подставить любой его подтип (класс-наследник), при этом работа программы не должна измениться.
I - Interface Segregation (принцип разделения интерфейсов)
Не нужно заставлять клиента (класс) реализовывать интерфейс, который не имеет к нему отношения.
D - Dependency Inversion (принцип инверсии зависимостей)
Модули верхнего уровня не должны зависеть от модулей нижнего уровня. И те, и другие должны зависеть от абстракции. Абстракции не должны зависеть от деталей. Детали должны зависеть от абстракций.
Рассмотрим первый принцип - Single Responsibility
Допустим у нас есть класс PcShopService и в нем есть несколько методов: найти пк по номеру, заказать пк, распечатать заказ, получить информацию о пк, отправить сообщение.
Java:
public class PcShopService {
public Pc findPc(String pcNum) {
// находим пк по номеру
return pc;
}
public Order orderPc(String pcNum, Client client) {
// клиент заказывает пк
return order;
}
public void printOrder(Order order) {
// печать заказа
}
public void getPcInfo(String pcType) {
if (pcType.equals("firstPc")) {
// что-то делаем
}
if (pcType.equals("secondPc")) {
// что-то делаем
}
if (pcType.equals("thirdPc")) {
// что-то делаем
}
}
public void sendMessage(String typeMessage, String message) {
if (typeMessage.equals("email")) {
// отправляем сообщение на почту
}
}
}
У данного класса есть несколько зон ответственности, что является нарушением первого принципа. Возьмем метод получения информации о пк. Теперь у нас есть только три типа пк firstPc, secondPc и thirdPc, но если человек захочет добавить еще несколько типов, тогда придется изменять и дописывать данный метод.
Или возьмем метод отправки сообщения. Если кроме отправки сообщения по электронной почте необходимо будет добавить отправку смс, то также необходимо будет изменять данный метод.
Одним словом, данный класс нарушает принцип единой ответственности, так как отвечает за разные действия.
Необходимо разделить данный класс PcShopService на несколько, и тем самым, следуя принципу единой ответственности, предоставить каждому классу отвечать только за одну зону или действие, так в дальнейшем его будет проще дополнять и модифицировать.
Необходимо создать класс PrinterService и вынести там функционал по печати.
Java:
public class PrinterService {
public void printOrder(Order order) {
// печать заказа
}
}
Java:
public class PcInfoService {
public void getPcInfo(String pcType) {
if (pcType.equals("firstPc")) {
// что-то делаем
}
if (pcType.equals("secondPc")) {
// что-то делаем
}
if (pcType.equals("thirdPc")) {
// что-то делаем
}
}
}
Java:
public class NotificationService {
public void sendMessage(String typeMessage, String message) {
if (typeMessage.equals("email")) {
// отправляем сообщение на почту
}
}
}
Java:
public class PcService {
public Pc findPc(String pcNum) {
// находим пк по номеру
return pc;
}
}
Java:
public class PcShopService {
public Order orderPc(String pcNum, Client client) {
// клиент заказывает пк
return order;
}
}
Принцип открытости-закрытости (Open Closed) рассмотрим на примере только что созданного класса по отправке сообщений.
Java:
public class NotificationService {
public void sendMessage(String typeMessage, String message) {
if (typeMessage.equals("email")) {
// отправляем сообщение на почту
}
}
}
Java:
public class NotificationService {
public void sendMessage(String typeMessage, String message) {
if (typeMessage.equals("email")) {
// отправляем сообщение на почту
}
if (typeMessage.equals("sms")) {
// отправляем сообщение на телефон
}
}
}
Но в данном случае мы нарушим второй принцип, потому что класс должен быть закрыт для модификации, но открыт для расширения, а мы модифицируем (изменяем) метод.
Для того чтобы придерживаться принципа открытости-закрытости нам необходимо спроектировать наш код таким образом, чтобы каждый мог повторно использовать нашу функцию, просто расширив ее. Поэтому создадим интерфейс NotificationService и в нем поместим метод sendMessage.
Java:
public interface NotificationService {
public void sendMessage(String message);
}
Java:
public class EmailNotification implements NotificationService {
@Override
public void sendMessage(String message) {
// отправляем сообщение на почту
}
}
Java:
public class MobileNotification implements NotificationService {
@Override
public void sendMessage(String message) {
// отправляем сообщение на телефон
}
}
Третий принцип: Liskov Substitution
Данный принцип связан с наследованием классов. Допустим у нас есть базовый класс Account, в котором есть три метода: просмотр остатка на счете, пополнение счета и оплата.
Java:
public class Account {
public BigDecimal balance(String numberAccount){
// логика
return bigDecimal;
};
public void refill(String numberAccount, BigDecimal sum){
// логика
}
public void payment(String numberAccount, BigDecimal sum){
// логика
}
}
Java:
public class SalaryAccount extends Account {
@Override
public BigDecimal balance(String numberAccount){
// логика
return bigDecimal;
};
@Override
public void refill(String numberAccount, BigDecimal sum){
// логика
}
@Override
public void payment(String numberAccount, BigDecimal sum){
// логика
}
}
Java:
public class DepositAccount extends Account {
@Override
public BigDecimal balance(String numberAccount){
// логика
return bigDecimal;
};
@Override
public void refill(String numberAccount, BigDecimal sum){
// логика
}
@Override
public void payment(String numberAccount, BigDecimal sum){
throw new UnsupportedOperationException();
}
}
Для того чтобы следовать принципу необходимо в родительский класс выносить только общую логику, характерную для классов наследников, которые будут ее реализовывать и, соответственно, можно будет базовый класс без проблем заменить на его класс-наследник.
Java:
public class Account {
public BigDecimal balance(String numberAccount){
// логика
return bigDecimal;
};
public void refill(String numberAccount, BigDecimal sum){
// логика
}
}
Java:
public class DepositAccount extends Account{
@Override
public BigDecimal balance(String numberAccount){
// логика
return bigDecimal;
};
@Override
public void refill(String numberAccount, BigDecimal sum){
// логика
}
}
Java:
public class PaymentAccount extends Account{
public void payment(String numberAccount, BigDecimal sum){
// логика
}
}
Java:
public class SalaryAccount extends PaymentAccount{
@Override
public BigDecimal balance(String numberAccount){
// логика
return bigDecimal;
};
@Override
public void refill(String numberAccount, BigDecimal sum){
// логика
}
@Override
public void payment(String numberAccount, BigDecimal sum){
// логика
}
}
Принцип разделения интерфейсов (Interface Segregation)
Допустим у нас есть интерфейс Payments и в нем есть три метода: оплата WebMoney, оплата банковской картой и оплата по номеру.
Java:
public interface Payments {
void payWebMoney();
void payCreditCard();
void payPhoneNumber();
}
Java:
public class InternetPaymentService implements Payments {
@Override
public void payWebMoney() {
// логика
}
@Override
public void payCreditCard() {
// логика
}
@Override
public void payPhoneNumber() {
// логика
}
}
Java:
public class TerminalPaymentService implements Payments {
@Override
public void payWebMoney() {
// логика
}
@Override
public void payCreditCard() {
// логика
}
@Override
public void payPhoneNumber() {
// логика
}
}
Таким образом произойдет нарушение принципа разделения интерфейсов.
Для того чтобы этого не происходило необходимо разделить наш исходный интерфейс Payments на несколько и, создавая классы, имплементить в них только те интерфейсы с методами, которые им нужны.
Java:
public interface WebMoneyPayment {
void payWebMoney();
}
Java:
public interface CreditCardPayment {
void payCreditCard();
}
Java:
public interface PhoneNumberPayment {
void payPhoneNumber();
}
Java:
public class InternetPaymentService implements WebMoneyPayment, CreditCardPayment, PhoneNumberPayment {
@Override
public void payWebMoney() {
// логика
}
@Override
public void payCreditCard() {
// логика
}
@Override
public void payPhoneNumber() {
// логика
}
}
Java:
public class TerminalPaymentService implements WebMoneyPayment, CreditCardPayment {
@Override
public void payWebMoney() {
// логика
}
@Override
public void payCreditCard() {
// логика
}
}
Рассмотрим последний принцип: принцип инверсии зависимостей (Dependency Inversion)
Допустим мы пишем приложение для магазина и решаем вопросы с проведением оплат. Вначале это просто небольшой магазин, где оплата происходит только за наличные. Создаем класс Cash и класс Shop.
Java:
public class Cash {
public void doTransaction(BigDecimal amount){
// логика
}
}
Java:
public class Shop {
private Cash cash;
public Shop(Cash cash) {
this.cash = cash;
}
public void doPayment(Object order, BigDecimal amount){
cash.doTransaction(amount);
}
}
Поэтому создадим интерфейс Payments.
Java:
public interface Payments {
void doTransaction(BigDecimal amount);
}
Java:
public class Cash implements Payments {
@Override
public void doTransaction(BigDecimal amount) {
// логика
}
}
Java:
public class BankCard implements Payments {
@Override
public void doTransaction(BigDecimal amount) {
// логика
}
}
Java:
public class PayByPhone implements Payments {
@Override
public void doTransaction(BigDecimal amount) {
// логика
}
}
Java:
public class Shop {
private Payments payments;
public Shop(Payments payments) {
this.payments = payments;
}
public void doPayment(Object order, BigDecimal amount){
payments.doTransaction(amount);
}
}
Мы рассмотрели на примерах псевдокода все принципы SOLID, надеюсь кому-то будет это полезно.