Всичко, което трябва да знаете за солидните принципи в Java



В тази статия ще научите подробно какво представляват солидните принципи в java с примери и тяхното значение с реалния живот.

В света на (ООП), има много насоки, модели или принципи за проектиране. Пет от тези принципи обикновено са групирани заедно и са известни със съкращението SOLID. Докато всеки от тези пет принципа описва нещо конкретно, те също се припокриват така, че приемането на един от тях предполага или води до приемане на друг. В тази статия ще разберем ТВЪРДИТЕ принципи в Java.

История на ТВЪРДИТЕ Принципи в Java

Робърт С. Мартин даде пет обектно-ориентирани принципа на проектиране и за него се използва съкращението „S.O.L.I.D“. Когато използвате комбинирано всички принципи на S.O.L.I.D, за вас става по-лесно да разработвате софтуер, който може лесно да се управлява. Другите характеристики на използването на S.O.L.I.D са:





  • Избягва кодовите миризми.
  • Бързо рефрактор код.
  • Може да прави адаптивна или гъвкава разработка на софтуер.

Когато използвате принципа на S.O.L.I.D при кодирането си, започвате да пишете кода, който е едновременно ефективен и ефективен.



Какво е значението на S.O.L.I.D?

Solid представлява пет принципа на Java, които са:

  • С : Принцип на една отговорност
  • ИЛИ : Принцип отворено-затворено
  • L : Liskov substitution principle
  • Аз : Принцип на сегрегация на интерфейса
  • д : Принцип на инверсия на зависимостта

В този блог ще обсъдим подробно всичките пет SOLID принципа на Java.



Принцип на една отговорност в Java

Какво пише?

Робърт С. Мартин го описва като един клас трябва да носи само една единствена отговорност.

Според принципа на единната отговорност трябва да има само една причина, поради която даден клас трябва да бъде променен. Това означава, че класът трябва да има една задача. Този принцип често се определя като субективен.

Принципът може да бъде добре разбран с пример. Представете си, че има клас, който извършва следните операции.

  • Свързан с база данни

  • Прочетете някои данни от таблиците на базата данни

  • Накрая го запишете във файл.

Представяли ли сте си сценария? Тук класът има множество причини да се промени и малко от тях са модифицирането на изходния файл, приемането на нова база данни. Когато говорим за отговорност с един принцип, бихме казали, че има твърде много причини класът да се промени, следователно той не се вписва правилно в принципа на една отговорност.

Например автомобилният клас може да се стартира или спира, но задачата да го измиете принадлежи на класа CarWash. В друг пример класът Book има свойства да съхранява собственото си име и текст. Но задачата за отпечатване на книгата трябва да принадлежи на класа Book Printer. Класът Book Printer може да печата на конзола или друг носител, но такива зависимости се премахват от класа Book

Защо се изисква този принцип?

Когато се спазва принципът на единната отговорност, тестването е по-лесно. С една отговорност класът ще има по-малко тестови случаи. По-малкото функционалност означава и по-малко зависимости от други класове. Това води до по-добра организация на кода, тъй като по-малките и добре предназначени класове са по-лесни за търсене.

Пример за изясняване на този принцип:

Да предположим, че сте помолени да внедрите услуга UserSetting, при която потребителят може да промени настройките, но преди това потребителят трябва да бъде удостоверен. Един от начините да се приложи това би бил:

публичен клас UserSettingService {public void changeEmail (потребител на потребителя) {if (checkAccess (потребител)) {// Опция за предоставяне на възможност за промяна}} public boolean checkAccess (потребител на потребител) {// Проверете дали потребителят е валиден. }}

Всичко изглежда добре, докато не искате да използвате повторно кода на checkAccess на друго място ИЛИ искате да направите промени в начина, по който се прави checkAccess. Във всички 2 случая в крайна сметка ще промените един и същи клас и в първия случай ще трябва да използвате UserSettingService, за да проверите и за достъп.
Един от начините да се коригира това е да се разложи UserSettingService в UserSettingService и SecurityService. И преместете кода checkAccess в SecurityService.

публичен клас UserSettingService {public void changeEmail (потребител на потребителя) {if (SecurityService.checkAccess (потребител)) {// Опция за предоставяне на опция}}} публичен клас SecurityService {публичен статичен булев checkAccess (потребител на потребителя) {// проверете достъпа. }}

Отворете затворен принцип в Java

Робърт С. Мартин го описва като софтуерни компоненти трябва да бъдат отворени за разширение, но затворени за модификация.

За да бъдем точни, според този принцип клас трябва да бъде написан по такъв начин, че да изпълнява работата си безупречно, без да се предполага, че хората в бъдеще просто ще дойдат и ще го променят. Следователно класът трябва да остане затворен за модификация, но трябва да има опцията да бъде разширен. Начините за разширяване на класа включват:

  • Наследяване от класа

  • Презаписване на необходимото поведение от класа

  • Разширяване на определено поведение на класа

Отличен пример за принцип на отворено-затворено може да се разбере с помощта на браузъри. Помните ли, че сте инсталирали разширения във вашия браузър Chrome?

Основната функция на браузъра chrome е да сърфира в различни сайтове. Искате ли да проверите граматиката, когато пишете имейл с браузър chrome? Ако отговорът е да, можете просто да използвате разширението Grammarly, тя ви осигурява проверка на граматиката на съдържанието.

Този механизъм, в който добавяте неща за увеличаване на функционалността на браузъра, е разширение. Следователно браузърът е идеален пример за функционалност, която е отворена за разширение, но е затворена за модификация. С прости думи, можете да подобрите функционалността, като добавите / инсталирате плъгини в браузъра си, но не можете да създадете нищо ново.

Защо се изисква този принцип?

OCP е важен, тъй като класовете могат да дойдат при нас чрез библиотеки на трети страни. Трябва да можем да разширяваме тези класове, без да се притесняваме дали тези базови класове могат да поддържат нашите разширения. Но наследяването може да доведе до подкласове, които зависят от изпълнението на базовия клас. За да се избегне това, се препоръчва използването на интерфейси. Тази допълнителна абстракция води до хлабаво свързване.

Да кажем, че трябва да изчислим площи с различни форми. Започваме със създаването на клас за нашия правоъгълник с първа формакойто има 2 атрибута дължина& ширина.

публичен клас Правоъгълник {публична двойна дължина обществена двойна ширина}

След това създаваме клас за изчисляване на площта на този правоъгълниккойто има метод calcuRectangleAreaкойто взема Правоъгълникакато входен параметър и изчислява неговата площ.

публичен клас AreaCalculator {публично двойно изчисляванеRectangleArea (правоъгълник с правоъгълник) {върнете правоъгълник.дължина * правоъгълник.ширина}}

Дотук добре. Сега да кажем, че получаваме нашия кръг от втора форма. Така че ние незабавно създаваме нов клас Circleс един радиус на атрибут.

публичен клас Circle {публичен двоен радиус}

След това модифицираме Areacalculatorклас за добавяне на изчисления на кръг чрез нов метод calcuCircleaArea ()

публичен клас AreaCalculator {публичен двоен калкулаторRectangleArea (правоъгълник с правоъгълник) {върнете правоъгълник.дължина * правоъгълник.ширина} публичен двоен калкулаторCircleArea (кръг на кръг) {return (22/7) * circle.radius * circle.radius}}

Имайте предвид обаче, че в начина, по който проектирахме нашето решение по-горе, имаше недостатъци.

Да кажем, че имаме нова форма петоъгълник. В този случай отново ще модифицираме класа AreaCalculator. Тъй като видовете фигури нарастват, това става по-объркващо, тъй като AreaCalculator продължава да се променя и всички потребители от този клас ще трябва да продължават да актуализират своите библиотеки, които съдържат AreaCalculator. В резултат на това класът AreaCalculator няма да бъде изравнен (финализиран) със сигурност, тъй като всеки път, когато се появи нова форма, той ще бъде модифициран. Така че, този дизайн не е затворен за модификация.

AreaCalculator ще трябва да продължи да добавя своята изчислителна логика в по-нови методи. Ние всъщност не разширяваме обхвата на фигурите, а просто правим решение на парчета (бит по бит) за всяка добавена форма.

Модификация на горния дизайн в съответствие с принципа на отваряне / затваряне:

Нека сега видим по-елегантен дизайн, който разрешава недостатъците в горния дизайн, като се придържа към принципа на отворен / затворен. Първо ще направим дизайна разширяващ се. За целта първо трябва да дефинираме базовия тип Shape и да имаме Circle & Rectangle, който реализира интерфейса Shape.

публичен интерфейс Shape {публична двойна калкулацияArea ()} публичен клас Правоъгълник реализира Shape {двойна дължина двойна ширина public двойна calcuArea () {return дължина * ширина}} публичен клас Кръг реализира Shape {публичен двоен радиус public double calcuArea () {return (22 / 7) * радиус * радиус}}

Има основен интерфейс Shape. Всички фигури вече изпълняват основния интерфейс Shape. Интерфейсът на формата има абстрактен метод CalcuArea (). И кръгът, и правоъгълникът предоставят свое собствено заменено изпълнение на метода calcuArea (), използвайки свои собствени атрибути.
Внесохме известна степен на разтегливост, тъй като фигурите вече са екземпляр на Shape интерфейси. Това ни позволява да използваме Shape вместо отделни класове
Последната точка по-горе споменати потребител на тези форми. В нашия случай потребителят ще бъде класът AreaCalculator, който сега ще изглежда така.

публичен клас AreaCalculator {публична двойна калкулацияShapeArea (Shape shape) {return shape.calculateArea ()}}

Този AreaCalculatorclass сега напълно премахва нашите дизайнерски недостатъци, отбелязани по-горе, и дава чисто решение, което се придържа към отворения затворен принцип. Нека да продължим с други ТВЪРДИ Принципи в Java

Liskov Substitution Principle in Java

Робърт С. Мартин го описва като производни типове трябва да бъдат напълно заместими за техните базови типове.

Принципът на заместване на Лисков приема q (x) за свойство, доказуемо за обекти от x, което принадлежи към тип Т. Сега, според този принцип, q (y) трябва да бъде доказуемо за обекти y, които принадлежат към тип S, и S всъщност е подтип на Т. Сега обърка ли се и не знаете какво всъщност означава принципът на заместване на Лисков? Определението му може да е малко сложно, но всъщност е доста лесно. Единственото нещо е, че всеки подклас или производен клас трябва да бъде заменим от своя родител или основен клас.

Можете да кажете, че това е уникален обектно-ориентиран принцип. Принципът може допълнително да бъде опростен от тип дете от определен тип родител, без да прави усложнения или да взривява нещата, трябва да има способността да се застъпва за този родител. Този принцип е тясно свързан с принципа на заместване на Лисков.

Защо се изисква този принцип?

Това избягва злоупотребата с наследството. Помага ни да се съобразим с връзката „е-а“. Можем също да кажем, че подкласовете трябва да изпълняват договор, определен от базовия клас. В този смисъл е свързано сПроектиране по договортова беше описано за първи път от Бертран Майер. Например изкушаващо е да се каже, че кръгът е вид елипса, но кръговете нямат две фокуси или големи / малки оси.

LSP се обяснява популярно, като се използват примери за квадрат и правоъгълник. ако приемем връзката ISA между квадрат и правоъгълник. По този начин ние наричаме „Квадратът е правоъгълник“. Кодът по-долу представлява връзката.

публичен клас Rectangle {private int length private int widthth public int getLength () {return length} public void setLength (int length) {this.length = length} public int getBreadth () {return widthth} public void setBreadth (int widthth) { this.breadth = widthth} public int getArea () {return this.length * this.breadth}}

По-долу е кодът за Square. Имайте предвид, че Square разширява Rectangle.

публичен клас Square разширява правоъгълник {public void setBreadth (int width) {super.setBreadth (width) super.setLength (width)} public void setLength (int length) {super.setLength (length) super.setBreadth (length)}}

В този случай ние се опитваме да установим ISA връзка между Square и Rectangle, така че извикването на „Square is a Rectangle“ в кода по-долу да започне да се държи неочаквано, ако бъде предаден екземпляр на Square. В случай на проверка за „Площ“ и проверка за „Ширина“ ще бъде изведена грешка в твърдението, въпреки че програмата ще прекрати, тъй като грешката в твърдението е хвърлена поради неуспеха на проверката на площта.

публичен клас LSPDemo {публична невалидна калкулираща площ (правоъгълник r) {r.setBreadth (2) r.setLength (3) assert r.getArea () == 6: printError ('area', r) assert r.getLength () == 3: printError ('length', r) assert r.getBreadth () == 2: printError ('širina', r)} private String printError (String errorIdentifer, Rectangle r) {return 'Неочаквана стойност на' + errorIdentifer + ' например на '+ r.getClass (). getName ()} public static void main (String [] args) {LSPDemo lsp = new LSPDemo () // Предава се екземпляр на Rectangle lsp.calculateArea (new Rectangle ()) // Предава се екземпляр на Square lsp.calculateArea (нов Square ())}}

Класът демонстрира Принципа на заместване на Лисков (LSP) Според принципа, функциите, които използват препратки към базовите класове, трябва да могат да използват обекти от производен клас, без да го знаят.

По този начин, в примера, показан по-долу, функцията calcuArea, която използва препратката към „Rectangle“, трябва да може да използва обектите от производен клас като Square и да изпълнява изискването, поставено от дефиницията на Rectangle. Трябва да се отбележи, че според дефиницията за правоъгълник, следното винаги трябва да е вярно, като се имат предвид данните по-долу:

  1. Дължината винаги трябва да бъде равна на дължината, предадена като вход към метода, setLength
  2. Широчината винаги трябва да е равна на широчината, предадена като вход към метода, setBreadth
  3. Площта винаги трябва да бъде равна на произведението на дължина и ширина

В случай, че се опитваме да установим ISA връзка между Square и Rectangle така, че да наричаме „Square is a Rectangle“, горният код ще започне да се държи неочаквано, ако бъде предаден екземпляр на Square В случай на проверка за площ и проверка ще се изведе грешка в твърдението за широчина, въпреки че програмата ще прекрати, тъй като грешката на твърдението е хвърлена поради неуспех на проверката на площ.

Класът Square не се нуждае от методи като setBreadth или setLength. Класът LSPDemo ще трябва да знае подробностите за производни класове на Rectangle (като Square), за да кодира по подходящ начин, за да избегне грешка при хвърляне. Промяната в съществуващия код на първо място нарушава принципа отворен-затворен.

Принцип на сегрегация на интерфейса

Робърт С. Мартин го описва, тъй като клиентите не трябва да бъдат принуждавани да прилагат ненужни методи, които те няма да използват.

СпоредПринцип на сегрегация на интерфейсаклиент, независимо от това, което никога не трябва да бъде принуждавано да внедрява интерфейс, който той не използва, или клиентът никога не трябва да бъде задължен да зависи от някакъв метод, който не се използва от тях. интерфейси, които са малки, но специфични за клиента, вместо монолитен и по-голям интерфейс. Накратко, би било зле за вас да принудите клиента да зависи от определено нещо, от което те не се нуждаят.

Например, един интерфейс за регистриране за писане и четене на дневници е полезен за база данни, но не и за конзола. Четенето на дневници няма смисъл за конзолния регистратор. Продължавайки с тази ТВЪРДА Принципи в Java статия.

Защо се изисква този принцип?

Нека да кажем, че има ресторантски интерфейс, който съдържа методи за приемане на поръчки от онлайн клиенти, клиенти за набиране или телефон и клиенти за влизане. Също така съдържа методи за обработка на онлайн плащания (за онлайн клиенти) и лични плащания (за клиенти, които влизат, както и телефонни клиенти, когато поръчката им се доставя у дома).

Сега нека създадем Java интерфейс за ресторант и го наречем RestaurantInterface.java.

публичен интерфейс RestaurantInterface {public void acceptOnlineOrder () public void takeTelephoneOrder () public void payOnline () public void walkInCustomerOrder () public void payInPerson ()}

Има 5 метода, дефинирани в RestaurantInterface, които са за приемане на онлайн поръчка, вземане на телефонна поръчка, приемане на поръчки от клиент, който приема, приемане на онлайн плащане и приемане на плащане лично.

Нека започнем с внедряването на RestaurantInterface за онлайн клиенти като OnlineClientImpl.java

публичен клас OnlineClientImpl реализира RestaurantInterface {public void acceptOnlineOrder () {// логика за поставяне на онлайн поръчка} public void takeTelephoneOrder () {// Неприложимо за онлайн поръчка хвърляне на нова UnsupportedOperationException ()} публична невалидна payOnline () {// логика за плащане online} обществена невалидна walkInCustomerOrder () {// Неприложима за онлайн поръчка хвърляне на нов UnsupportedOperationException ()} публична невалидна payInPerson () {// Неприложима за онлайн поръчка хвърляне на нова UnsupportedOperationException ()}}
  • Тъй като горният код (OnlineClientImpl.java) е за онлайн поръчки, хвърлете UnsupportedOperationException.

  • Онлайн, телефонни и входящи клиенти използват изпълнението на RestaurantInterface, специфично за всеки от тях.

  • Класовете за внедряване на клиент за телефонен разговор и клиент за ходене ще имат неподдържани методи.

    факториал, използващ рекурсия в c
  • Тъй като 5-те метода са част от RestaurantInterface, класовете за внедряване трябва да внедрят всичките 5 от тях.

  • Методите, които всеки от класовете за изпълнение хвърля UnsupportedOperationException. Както можете ясно да видите - прилагането на всички методи е неефективно.

  • Всяка промяна в някой от методите на RestaurantInterface ще бъде разпространена във всички класове за изпълнение. Тогава поддръжката на кода започва да става наистина тромава и регресивните ефекти от промените ще продължат да се увеличават.

  • RestaurantInterface.java нарушава принципа на единична отговорност, тъй като логиката за плащания, както и тази за поставяне на поръчки, са групирани в един интерфейс.

За да преодолеем гореспоменатите проблеми, ние прилагаме Принципа за разделяне на интерфейса, за да рефакторираме горния дизайн.

  1. Разделете функционалностите за плащане и разположение на поръчки в два отделни стройни интерфейса, PaymentInterface.java и OrderInterface.java.

  2. Всеки от клиентите използва по една реализация на PaymentInterface и OrderInterface. Например - OnlineClient.java използва OnlinePaymentImpl и OnlineOrderImpl и т.н.

  3. Принципът на единична отговорност вече е приложен като интерфейс за плащане (PaymentInterface.java) и интерфейс за поръчка (OrderInterface).

  4. Промяната в някой от интерфейсите за поръчки или плащания не засяга другия. Сега те са независими. Няма да е необходимо да правите фиктивна реализация или да хвърляте UnsupportedOperationException, тъй като всеки интерфейс има само методи, които винаги ще използва.

След прилагане на ISP

Принцип на инверсия на зависимостта

Робърт С. Мартин го описва, тъй като зависи от абстракциите, а не от конкрециите. Според него модулът от високо ниво никога не трябва да разчита на модул от ниско ниво. например

Отиваш в местен магазин, за да купиш нещо и решаваш да платиш за това, като използваш дебитната си карта. Така че, когато дадете картата си на служителя за извършване на плащането, служителят не се притеснява да провери каква карта сте дали.

Дори ако сте дали карта Visa, той няма да извади машина Visa за прекарване на картата ви. Видът на кредитната или дебитната карта, който имате за плащане, дори няма значение, те просто ще го прекарат. И така, в този пример можете да видите, че и вие, и служителят зависи от абстракцията на кредитната карта и не се притеснявате от спецификата на картата. Това е принципът на инверсия на зависимост.

Защо се изисква този принцип?

Той позволява на програмист да премахва твърдо кодирани зависимости, така че приложението да стане свободно свързано и разширяемо.

публичен клас студент {частен адрес адрес публичен студент () {адрес = нов адрес ()}}

В горния пример студентският клас изисква обект Address и той отговаря за инициализирането и използването на обекта Address. Ако адресният клас бъде променен в бъдеще, трябва да направим промени и в студентския клас. Това прави тясното свързване между обектите на студент и адрес. Можем да разрешим този проблем, като използваме шаблона за проектиране на инверсия на зависимости. т.е.адресният обект ще бъде реализиран независимо и ще бъде предоставен на Student, когато Student е създаден с помощта на конструктор или базирана на setter инверсия на зависимост.

С това стигнахме до края на тези ТВЪРДИ Принципи в Java.

Вижте от Edureka, доверена компания за онлайн обучение с мрежа от над 250 000 доволни учащи, разпространени по целия свят. Курсът за обучение и сертифициране на Java J2EE и SOA на Edureka е предназначен за студенти и професионалисти, които искат да бъдат Java Developer. Курсът е предназначен да ви даде начален старт в програмирането на Java и да ви обучи както за основни, така и за разширени Java концепции, заедно с различни Java рамки като Hibernate & Spring.

Имате въпрос към нас? Моля, споменете го в раздела за коментари на този блог „ТВЪРДИ принципи в Java“ и ние ще се свържем с вас възможно най-скоро.