Java의 견고한 원칙에 대해 알아야 할 모든 것



이 기사에서는 예제와 함께 Java의 Solid 원칙이 무엇인지 그리고 실제 예제에서 그 중요성에 대해 자세히 배웁니다.

세계에서 (OOP), 많은 디자인 지침, 패턴 또는 원칙이 있습니다. 이러한 원칙 중 다섯 가지는 일반적으로 함께 그룹화되며 SOLID라는 약어로 알려져 있습니다. 이 다섯 가지 원칙은 각각 특정 사항을 설명하지만, 그 중 하나를 채택하는 것은 다른 하나를 채택하는 것을 의미하거나 이르게하기 위해 중첩됩니다. 이 기사에서는 Java의 SOLID 원칙을 이해합니다.

Java의 SOLID 원칙의 역사

Robert C. Martin은 5 가지 객체 지향 디자인 원칙을 제시했으며이를 위해 '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의 5 가지 원칙을 나타냅니다.

  • 에스 : 단일 책임 원칙
  • 또는 : 개방-폐쇄 원리
  • : Liskov 대체 원리
  • 나는 : 인터페이스 분리 원칙
  • : 종속성 반전 원리

이 블로그에서는 Java의 5 가지 SOLID 원칙에 대해 자세히 설명합니다.



Java의 단일 책임 원칙

그것은 무엇을 말하는가?

Robert C. Martin은 한 클래스가 단 하나의 책임 만 가져야한다고 설명합니다.

단일 책임 원칙에 따르면 클래스를 변경해야하는 이유는 하나만 있어야합니다. 이것은 클래스가 할 일이 하나 있어야한다는 것을 의미합니다. 이 원칙은 종종 주관적이라고합니다.

원리는 예를 들어 잘 이해할 수 있습니다. 다음 작업을 수행하는 클래스가 있다고 상상해보십시오.

  • 데이터베이스에 연결됨

  • 데이터베이스 테이블에서 일부 데이터 읽기

  • 마지막으로 파일에 씁니다.

시나리오를 상상 했습니까? 여기에서 클래스는 변경해야 할 여러 가지 이유가 있으며 그중 일부는 파일 출력 수정, 새로운 데이터베이스 채택입니다. 우리가 단일 원칙 책임에 대해 이야기 할 때, 우리는 클래스가 변경되는 이유가 너무 많아서 단일 책임 원칙에 적절하게 맞지 않는다고 말할 것입니다.

예를 들어, Automobile 클래스는 스스로 시작하거나 중지 할 수 있지만 세척 작업은 CarWash 클래스에 속합니다. 또 다른 예에서 Book 클래스에는 자체 이름과 텍스트를 저장하는 속성이 있습니다. 그러나 책을 인쇄하는 작업은 Book Printer 클래스에 속해야합니다. Book Printer 클래스는 콘솔이나 다른 매체로 인쇄 할 수 있지만 이러한 종속성은 Book 클래스에서 제거됩니다.

이 원칙이 필요한 이유는 무엇입니까?

단일 책임 원칙을 따르면 테스트가 더 쉬워집니다. 단일 책임으로 클래스는 더 적은 수의 테스트 케이스를 갖게됩니다. 기능이 적다는 것은 다른 클래스에 대한 종속성도 적다는 것을 의미합니다. 작고 목적이 좋은 클래스가 검색하기 더 쉽기 때문에 더 나은 코드 구성으로 이어집니다.

이 원칙을 명확히하는 예 :

사용자가 설정을 변경할 수 있지만 그 전에 사용자를 인증해야하는 UserSetting 서비스를 구현하라는 요청을 받았다고 가정합니다. 이를 구현하는 한 가지 방법은 다음과 같습니다.

public class UserSettingService {public void changeEmail (User user) {if (checkAccess (user)) {// 변경 옵션 부여}} public boolean checkAccess (User user) {// 사용자가 유효한지 확인합니다. }}

다른 곳에서 checkAccess 코드를 재사용하거나 checkAccess가 수행되는 방식을 변경하고 싶을 때까지 모두 괜찮아 보입니다. 두 경우 모두 동일한 클래스를 변경하게되며 첫 번째 경우에도 UserSettingService를 사용하여 액세스를 확인해야합니다.
이를 수정하는 한 가지 방법은 UserSettingService를 UserSettingService 및 SecurityService로 분해하는 것입니다. 그리고 checkAccess 코드를 SecurityService로 이동합니다.

public class UserSettingService {public void changeEmail (User user) {if (SecurityService.checkAccess (user)) {// 변경 옵션 부여}}} public class SecurityService {public static boolean checkAccess (User user) {// 액세스를 확인합니다. }}

자바에서 개방형 폐쇄 원칙

Robert C. Martin은 확장을 위해 소프트웨어 구성 요소를 열어야하지만 수정을 위해 닫아야한다고 설명합니다.

문자열을 읽는 스캐너 클래스 메소드

정확히 말하면이 원칙에 따라 클래스는 미래의 사람들이 단순히 와서 바꿀 것이라는 가정없이 완벽하게 작업을 수행하는 방식으로 작성되어야합니다. 따라서 클래스는 수정을 위해 닫힌 상태로 유지되어야하지만 확장 옵션이 있어야합니다. 클래스를 확장하는 방법은 다음과 같습니다.

  • 클래스에서 상속

  • 클래스에서 필요한 동작 덮어 쓰기

  • 클래스의 특정 동작 확장

개방형 폐쇄 원칙의 훌륭한 예는 브라우저의 도움으로 이해할 수 있습니다. 크롬 브라우저에 확장 프로그램을 설치 한 것을 기억하십니까?

크롬 브라우저의 기본 기능은 다른 사이트를 서핑하는 것입니다. 크롬 브라우저를 사용하여 이메일을 작성할 때 문법을 확인하고 싶으신가요? 그렇다면 Grammarly 확장 기능을 사용하시면됩니다. 콘텐츠에 대한 문법 검사를 제공합니다.

브라우저의 기능을 높이기 위해 항목을 추가하는이 메커니즘은 확장 기능입니다. 따라서 브라우저는 확장을 위해 열려 있지만 수정을 위해 닫혀있는 기능의 완벽한 예입니다. 간단히 말해서 브라우저에 플러그인을 추가 / 설치하여 기능을 향상시킬 수 있지만 새로운 것을 빌드 할 수는 없습니다.

이 원칙이 필요한 이유는 무엇입니까?

OCP는 클래스가 타사 라이브러리를 통해 우리에게 올 수 있기 때문에 중요합니다. 이러한 기본 클래스가 확장을 지원할 수 있는지 걱정하지 않고 이러한 클래스를 확장 할 수 있어야합니다. 그러나 상속은 기본 클래스 구현에 의존하는 하위 클래스로 이어질 수 있습니다. 이를 방지하려면 인터페이스를 사용하는 것이 좋습니다. 이 추가 추상화는 느슨한 결합으로 이어집니다.

다양한 모양의 면적을 계산해야한다고 가정 해 보겠습니다. 첫 번째 모양 Rectangle에 대한 클래스를 만드는 것으로 시작합니다.2 개의 속성 길이& 너비.

공용 클래스 Rectangle {공용 이중 길이 공용 이중 너비}

다음으로이 Rectangle의 면적을 계산하는 클래스를 만듭니다.computeRectangleArea 메소드가 있습니다.Rectangle을입력 매개 변수로 면적을 계산합니다.

public class AreaCalculator {public double calculateRectangleArea (Rectangle rectangle) {return rectangle.length * rectangle.width}}

여태까지는 그런대로 잘됐다. 이제 두 번째 모양 원이 있다고 가정 해 보겠습니다. 그래서 우리는 즉시 새로운 클래스 Circle을 만듭니다.단일 속성 반경이 있습니다.

public class Circle {public double radius}

그런 다음 Areacalculator를 수정합니다.새로운 메소드 calculateCircleaArea ()를 통해 원 계산을 추가하는 클래스

public class AreaCalculator {public double calculateRectangleArea (Rectangle rectangle) {return rectangle.length * rectangle.width} public double calculateCircleArea (Circle circle) {return (22/7) * circle.radius * circle.radius}}

그러나 위의 솔루션을 설계 한 방식에 결함이있었습니다.

새로운 모양의 오각형이 있다고 가정 해 봅시다. 이 경우 다시 AreaCalculator 클래스를 수정하게됩니다. 모양의 유형이 커짐에 따라 AreaCalculator가 계속 변경되고이 클래스의 모든 소비자가 AreaCalculator가 포함 된 라이브러리를 계속 업데이트해야하므로 더 복잡해집니다. 결과적으로 AreaCalculator 클래스는 새 모양이 올 때마다 수정되므로 확실하게 기준선 (최종화)되지 않습니다. 따라서이 디자인은 수정을 위해 닫히지 않습니다.

AreaCalculator는 새로운 메서드에서 계산 논리를 계속 추가해야합니다. 우리는 실제로 모양의 범위를 확장하는 것이 아니라 단순히 추가되는 모든 모양에 대해 조각 (비트 단위) 솔루션을 수행하는 것입니다.

개방 / 폐쇄 원칙을 준수하기 위해 위의 디자인 수정 :

이제 Open / Closed Principle을 고수하여 위 디자인의 결점을 해결하는보다 우아한 디자인을 살펴 보겠습니다. 먼저 디자인을 확장 가능하게 만들 것입니다. 이를 위해 먼저 기본 유형 Shape를 정의하고 Circle & Rectangle이 Shape 인터페이스를 구현하도록해야합니다.

public interface Shape {public double calculateArea ()} public class Rectangle은 Shape {double length double width public double calculateArea () {return length * width}}를 구현합니다. public class Circle은 Shape {public double radius public double calculateArea () {return (22 / 7) * 반지름 * 반경}}

기본 인터페이스 Shape가 있습니다. 이제 모든 셰이프가 기본 인터페이스 Shape를 구현합니다. 모양 인터페이스에는 추상 메서드 calculateArea ()가 있습니다. 원과 직사각형은 모두 고유 한 속성을 사용하여 calculateArea () 메서드의 재정의 된 구현을 제공합니다.
셰이프가 이제 셰이프 인터페이스의 인스턴스이므로 어느 정도 확장 성을 가져 왔습니다. 이를 통해 개별 클래스 대신 Shape를 사용할 수 있습니다.
위에서 언급 한 마지막 요점은 이러한 모양의 소비자입니다. 우리의 경우 소비자는 이제 다음과 같은 AreaCalculator 클래스가됩니다.

public class AreaCalculator {public double calculateShapeArea (Shape shape) {return shape.calculateArea ()}}

이 AreaCalculator클래스는 이제 위에서 언급 한 설계 결함을 완전히 제거하고 개방-폐쇄 원칙을 준수하는 깨끗한 솔루션을 제공합니다. Java의 다른 SOLID 원칙을 살펴 보겠습니다.

자바의 Liskov 대체 원리

Robert C. Martin은 파생 유형이 기본 유형을 완전히 대체 할 수 있어야한다고 설명합니다.

Liskov 대체 원칙은 q (x)를 T 유형에 속하는 x의 엔티티에 대해 증명할 수있는 속성이라고 가정합니다. 이제이 원칙에 따라 q (y)는 이제 유형 S에 속하는 객체 y에 대해 증명 가능해야합니다. S는 실제로 T의 하위 유형입니다. 이제 혼란스럽고 Liskov 대체 원리가 실제로 무엇을 의미하는지 모르십니까? 정의는 다소 복잡 할 수 있지만 실제로는 매우 쉽습니다. 유일한 것은 모든 하위 클래스 또는 파생 클래스가 부모 또는 기본 클래스로 대체 가능해야한다는 것입니다.

고유 한 객체 지향 원칙이라고 말할 수 있습니다. 이 원칙은 복잡한 부모 유형의 자식 유형에 의해 더 단순화 될 수 있으며, 그 부모를 대신 할 수있는 능력을 가져야하는 일을 부풀려 야합니다.이 원칙은 Liskov 대체 원칙과 밀접한 관련이 있습니다.

이 원칙이 필요한 이유는 무엇입니까?

이것은 상속을 오용하는 것을 방지합니다. 그것은 우리가 'is-a'관계를 따르는 데 도움이됩니다. 하위 클래스는 기본 클래스에서 정의한 계약을 이행해야한다고 말할 수도 있습니다. 이런 의미에서 그것은계약에 의한 설계Bertrand Meyer가 처음으로 설명했습니다. 예를 들어 원은 타원의 한 유형이지만 원에는 두 개의 초점 또는 장 / 단축이 없다고 말하고 싶을 것입니다.

LSP는 정사각형 및 직사각형 예제를 사용하여 널리 설명됩니다. Square와 Rectangle 사이의 ISA 관계를 가정하면. 따라서 우리는 '정사각형은 직사각형'이라고 부릅니다. 아래 코드는 관계를 나타냅니다.

public class Rectangle {private int length private int breadth public int getLength () {return length} public void setLength (int length) {this.length = length} public int getBreadth () {return width} public void setBreadth (int breadth) { this.breadth = breadth} public int getArea () {return this.length * this.breadth}}

아래는 Square의 코드입니다. Square는 Rectangle을 확장합니다.

자바 원격 메소드 호출 예제
public class Square extends Rectangle {public void setBreadth (int breadth) {super.setBreadth (breadth) super.setLength (breadth)} public void setLength (int length) {super.setLength (length) super.setBreadth (length)}}

이 경우 아래 코드에서 'Square is a Rectangle'을 호출하면 Square의 인스턴스가 전달되면 예기치 않게 동작하도록 Square와 Rectangle 간의 ISA 관계를 설정하려고합니다. “Area”를 확인하고“Breadth”를 확인하는 경우에는 Assertion 오류가 발생하지만 Area 확인 실패로 인해 assertion 오류가 발생하여 프로그램이 종료됩니다.

public class LSPDemo {public void calculateArea (Rectangle 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 ( 'breadth', r)} private String printError (String errorIdentifer, Rectangle r) {return 'Unexpected value of'+ errorIdentifer + ' 예를 들어 '+ r.getClass (). getName ()} public static void main (String [] args) {LSPDemo lsp = new LSPDemo () // Rectangle의 인스턴스가 전달됩니다. lsp.calculateArea (new Rectangle ()) // Square의 인스턴스가 전달됩니다. lsp.calculateArea (new Square ())}}

이 수업은 Liskov Substitution Principle (LSP)을 보여줍니다. 원칙에 따라 기본 클래스에 대한 참조를 사용하는 함수는 자신도 모르게 파생 클래스의 개체를 사용할 수 있어야합니다.

따라서 아래 표시된 예에서 'Rectangle'참조를 사용하는 함수 calculateArea는 Square와 같은 파생 클래스의 객체를 사용할 수 있어야하며 Rectangle 정의에서 제시하는 요구 사항을 충족해야합니다. Rectangle의 정의에 따라 아래 데이터를 고려할 때 다음은 항상 참이어야합니다.

  1. 길이는 항상 setLength 메소드에 대한 입력으로 전달 된 길이와 같아야합니다.
  2. Breadth는 항상 setBreadth 메소드에 입력으로 전달 된 너비와 같아야합니다.
  3. 면적은 항상 길이와 너비의 곱과 같아야합니다.

Square와 Rectangle 사이에 ISA 관계를 설정하여“Square is a Rectangle”이라고 부르는 경우 Square의 인스턴스가 전달되면 위의 코드가 예기치 않게 동작하기 시작합니다. 면적 확인시 Assertion 오류가 발생합니다. 영역 검사의 실패로 인해 어설 션 오류가 발생하면 프로그램이 종료됩니다.

Square 클래스에는 setBreadth 또는 setLength와 같은 메서드가 필요하지 않습니다. LSPDemo 클래스는 오류 발생을 방지하기 위해 적절하게 코딩하기 위해 Rectangle의 파생 클래스 (예 : Square)의 세부 정보를 알아야합니다. 기존 코드의 변경은 애초에 개방 폐쇄 원칙을 깨뜨립니다.

인터페이스 분리 원리

Robert C. Martin은 클라이언트가 사용하지 않을 불필요한 메서드를 구현하도록 강요해서는 안된다고 설명합니다.

에 따르면인터페이스 분리 원칙클라이언트가 사용하지 않는 인터페이스를 구현하도록 강요해서는 안되거나 클라이언트가 사용하지 않는 방법에 의존 할 의무가 없어도 기본적으로 인터페이스 분리 원칙은 사용자가 선호하는 인터페이스 분리 원칙입니다. 모 놀리 식 및 더 큰 인터페이스 대신 클라이언트에 따라 작지만 클라이언트에 따라 달라지는 인터페이스 간단히 말해서 클라이언트가 필요하지 않은 특정 항목에 의존하도록 강제하는 것은 좋지 않습니다.

예를 들어, 로그 쓰기 및 읽기를위한 단일 로깅 인터페이스는 데이터베이스에는 유용하지만 콘솔에는 유용하지 않습니다. 로그를 읽는 것은 콘솔 로거에게는 의미가 없습니다. 이 SOLID Principles in Java 기사로 넘어갑니다.

이 원칙이 필요한 이유는 무엇입니까?

온라인 고객, 전화 접속 또는 전화 고객 및 방문 고객의 주문을 수락하는 방법이 포함 된 레스토랑 인터페이스가 있다고 가정 해 보겠습니다. 또한 온라인 결제 (온라인 고객 용) 및 직접 결제 (방문 고객 및 주문이 집에서 배달되는 전화 고객 용)를 처리하는 방법도 포함되어 있습니다.

이제 Restaurant 용 Java 인터페이스를 만들고 이름을 RestaurantInterface.java로 지정하겠습니다.

public interface RestaurantInterface {public void acceptOnlineOrder () public void takeTelephoneOrder () public void payOnline () public void walkInCustomerOrder () public void payInPerson ()}

온라인 주문 접수, 전화 주문 접수, 방문 고객의 주문 접수, 온라인 결제 접수, 직접 결제 접수 등 5 가지 방법이 RestaurantInterface에 정의되어 있습니다.

온라인 고객을위한 RestaurantInterface를 OnlineClientImpl.java로 구현하여 시작하겠습니다.

public class OnlineClientImpl implements RestaurantInterface {public void acceptOnlineOrder () {// 온라인 주문을위한 로직} public void takeTelephoneOrder () {// 온라인 주문에 적용되지 않음 throw new UnsupportedOperationException ()} public void payOnline () {// 결제 로직 online} public void walkInCustomerOrder () {// 온라인 주문에 적용되지 않음 throw new UnsupportedOperationException ()} public void payInPerson () {// 온라인 주문에 적용되지 않음 throw new UnsupportedOperationException ()}}
  • 위 코드 (OnlineClientImpl.java)는 온라인 주문 용이므로 UnsupportedOperationException을 발생시킵니다.

  • 온라인, 전화 및 오프라인 클라이언트는 각각에 특정한 RestaurantInterface 구현을 사용합니다.

  • Telephonic 클라이언트 및 워크 인 클라이언트의 구현 클래스에는 지원되지 않는 메소드가 있습니다.

  • 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을 throw 할 필요가 없습니다.

ISP 적용 후

의존성 반전 원리

로버트 C. 마틴 (Robert C. Martin)은이를 구체화가 아닌 추상화에 의존하기 때문에이를 설명합니다. 이에 따르면 고수준 모듈은 저수준 모듈에 절대 의존해서는 안됩니다. 예를 들면

당신은 물건을 사기 위해 지역 상점에 가서 직불 카드를 사용하여 지불하기로 결정합니다. 따라서 지불을 위해 점원에게 카드를 줄 때 점원은 어떤 종류의 카드를 주 었는지 확인하지 않아도됩니다.

Visa 카드를 주셨다 고해도 카드를 긁기 위해 Visa 기계를 내놓지 않을 것입니다. 지불하는 데 사용하는 신용 ​​카드 또는 직불 카드의 유형은 중요하지 않습니다. 따라서이 예에서 귀하와 점원 모두 신용 카드 추상화에 의존하고 있으며 카드의 세부 사항에 대해 걱정하지 않는 것을 알 수 있습니다. 이것이 의존성 반전 원칙입니다.

이 원칙이 필요한 이유는 무엇입니까?

프로그래머가 하드 코딩 된 종속성을 제거하여 애플리케이션이 느슨하게 결합되고 확장 가능하도록 할 수 있습니다.

public class Student {개인 주소 주소 public Student () {주소 = new Address ()}}

위의 예에서 Student 클래스에는 Address 개체가 필요하며 Address 개체의 초기화 및 사용을 담당합니다. 향후 주소 클래스가 변경되면 학생 클래스도 변경해야합니다. 이렇게하면 Student와 Address 개체가 긴밀하게 결합됩니다. 종속성 반전 디자인 패턴을 사용하여이 문제를 해결할 수 있습니다. 즉, Address 객체는 독립적으로 구현되며 Student가 생성자 기반 또는 setter 기반 종속성 반전을 사용하여 인스턴스화 될 때 Student에게 제공됩니다.

이것으로 우리는 Java의 SOLID 원칙을 끝냅니다.

확인 전 세계에 걸쳐 250,000 명 이상의 만족 한 학습자 네트워크를 보유한 신뢰할 수있는 온라인 학습 회사 인 Edureka에서 작성했습니다. Edureka의 Java J2EE 및 SOA 교육 및 인증 과정은 Java 개발자가 되고자하는 학생과 전문가를 위해 설계되었습니다. 이 과정은 Java 프로그래밍을 시작하고 Hibernate & Spring과 같은 다양한 Java 프레임 워크와 함께 핵심 및 고급 Java 개념 모두에 대해 교육하도록 설계되었습니다.

질문이 있으십니까? 이 '자바의 솔리드 원칙'블로그의 댓글 섹션에 언급 해 주시면 가능한 한 빨리 답변을 드리겠습니다.