WRITING SOLID CODEпреразказ с елементи на разсъждение
Костадин Голев@kotsetogithub.com/kotse
SOLID CODE ДИЗАЙН ПРИНЦИПИ
• Първоначално наречени “първите пет принципа” от чичо Боб
• SOLID e акроним, съставен от първата буква на всеки принцип
• Целят да ни помогнат да пишем код, който е по-лесен за четене, поддръжка и надграждане
КОИ СА SOLID ПРИНЦИПИТЕ?
• (S)ingle Responsibility Principle
• (O)pen-Closed Principle
• (L)iskov Substitution Principle
• (I)nterface segregation Principle
• (D)ependency inversion Principle
КАК ГИ ИЗПОЛЗВАМЕ?
• Не са правила или закони
• Идеи, които ни помагат в взимане на решения
• Служат като средство да комуникираме дизайна на кода, който пишем
• Ако имаме усещането, че един код е добър или лош, често можем да намерим принцип, който да обясни това
КРУШКАТА И ВЕНТИЛАТОРА
SINGLE RESPONSIBILITY PRINCIPLE
Всеки клас/функция/променлива трябва да прави едно нещо и да го прави добре
По-просто - само една причина да се промени
class TaxiService { public void orderTaxi(String phoneNumber, String address) { if (phone.length() < 7 || phone.length() > 12 || phone.contains(BAD_CHARACTERS)) { throw new Exception("Phone number not valid!"); }
Taxi taxi = taxiPool.getTaxi(address);
smsClient.sendMessage(phoneNumber, “Taxi on the way!”); }}
class TaxiService { boolean validatePhoneNumber() { if (phone.length() < 7 || phone.length() > 12 || phone.contains(BAD_CHARACTERS)) {
return false; }
return true; }
public void orderTaxi(String phoneNumber, String address) { if (!validatePhoneNumber(phoneNumber)) { throw new Exception("Phone number not valid!"); }
Taxi taxi = taxiPool.getTaxi(address); smsClient.sendMessage(phoneNumber, “Taxi on the way!”); }}
class SMSService { boolean validatePhoneNumber() { if (phone.length() < 7 || phone.length() > 12 || phone.contains(BAD_CHARACTERS)) {
return false; }
return true; }
void sendSms(String phoneNumber, String message) { smsClient.sendMessage(phoneNumber, “Taxi on the way”); }}
class TaxiService { SMSService smsService;
public void orderTaxi(String phoneNumber, String address) { if (smsService.validatePhoneNumber(phoneNumber)) { throw new Exception("Phone number not valid!"); }
Taxi taxi = taxiPool.getTaxi(address); smsService.sendSMS(phoneNumber, “Taxi on the way!”); }}
SUMMARY
• Може би най-труден за прилагане от петте принципа
• По-четим и лесен за преизползване код
• Код в един метод се асоциира с името на метода
• Името на всеки метод се асоциира с името на класа
СТАРИЯ ТЕЛЕВИЗОР
OPEN-CLOSED PRINCIPLE
Кодът, който пишем трябва да е отворен за разширение и затворен за модификация
class PaymentService { int calculatePayment (int amount, Customer customer) { double discount = 0;
switch (customer.type()) { case SILVER : discount = 0.1; break; case GOLD : discount = 0.2; break; ... ... }
amountToPay = amount - amount*discount;
return amountToPay; }}
class Customer { double getDiscount() { return 0; }}
class SilverCustomer extends Customer { double getDiscount() { return 0.1; }}
class GoldCustomer extends Customer { double getDiscount() { return 0.2; }}
class PaymentService {
int calculatePayment (int amount, Customer customer) { double discount = customer.getDiscount();
amountToPay = amount - amount*discount;
return amountToPay; }}
SUMMARY
• Вместо да променяме код, който вече работи, надграждаме го
• Използваме наследяване за целта
• Нарушаване на принципа води до много трудна поддръжка на кода, тъй като една малка промяна в един клас води до множество промени в други
САЛАТЕНИЯ БАР
LISKOV SUBSTITUTION PRINCIPLE
Обектите в програмата могат да бъдат заменени от наследниците им без промяна в тяхното
поведение
public interface Duck { public void papa();}
public class RealDuck implements Duck { public void papa() { //papa code goes here! }}
public class ElectricDuck implements Duck { public void papa() { if (turnedOn) { //papa code goes here! } }}
Duck realDuck = new RealDuck();duck.papa(); //works!
Duck electricDuck = new ElectricDuck();duck.papa(); //does not work!
if (duck instanceof ElectricDuck) { ((ElectricDuck)duck).turnOn();}duck.papa(); //now works!
class ElectricDuck implements Duck { public void turnOn() { turnedOn = true }
public void papa() { if (!turnedOn) { turnOn(); } //papa code goes here! }}
Duck duck = new AnyKindOfDuck();duck.papa(); //just works with all Ducks now!
SUMMARY
• Ако бъде нарушен, даден обект нарушава обичайното си “поведение”
• На практика представлява допълнение към Open-Closed принципа, като ни "повелява", че не можем едновременно да надградим един модул и да променим неговото поведение
КНИГАТА С РЕЦЕПТИ
INTERFACE SEGREGATION PRINCIPLE
Не принуждавайте кода си за зависи от неща, от които няма нужда
public interface Worker { public void work(); public void getPaid();}
public class RegularWorker implements Worker { public void work() {} public void getPaid() {}}
public class Manager { Worker worker;
public void setWorker(Worker worker) { this.worker = worker; }
public manage () { worker.work(); }}
public class Robot implements Worker { public void work() {} public void getPaid() {} ???}
interface IWork { public void work();}
interface IGetPaid { public void getPaid();}
class Worker implements IWork, IGetPaid { @Override public void work() {
}
@Override public void getPaid() {
}}
class Robot implements IWork { @Override public void work() {
}}
class Manager { IWork worker;
void setWorker(IWork worker) { this.worker = worker; }
void manage() { worker.work(); }}
SUMMARY
• Да се пише код, който да е лесен за разбиране е не по-малко важно от това кода да работи
• Интерфейсите ни помагат да опишем как трябва да работи кода, който пишем
• Множество малки интерфейси е по-добре от това да имаме един голям т.нар "замърсен" интерфейс
ФАБРИКАТА В ЛОВЕЧ
DEPENDENCY INVERSION PRINCIPLE
Зависимостите в кода е добре да се основават на абстракции, а не конкретни неща
Модулите на по-високо ниво в кода не трябва да зависят от тези на по-ниско
public class Steed5 { Diesel130HPEngine engine;
public Steed5() { engine = new Diesel130HPEngine(); }
public void start() { engine.ignition(); }}
Steed5 car = new Steed5();car.start();
public class Steed5V8 { V8Engine engine;
public Steed5V8() { engine = new V8Engine(); }
public void start() { engine.ignition(); }}
public interface Engine { public void ignition();}
public class V8Engine implements Engine { public void ignition () {}}
Engine engine = new V8Engine();Steed5 car = new Steed5(engine);car.start();
public class Steed5 { Engine engine;
public Steed5(Engine engine) { this.engine = engine; }
public void start() { engine.ignition(); }}
SUMMARY
• При нарушаване на принципа поддръжката на кода се затруднява значително
• Ако един модул се променя, той не трябва да променя модулите на по-високо ниво от неговото
Благодаря за вниманието!