SOLID와 정적 방법


11

내가 자주 겪는 문제는 다음과 같습니다. Product 클래스가있는 웹 상점 프로젝트가 있습니다. 사용자가 제품에 리뷰를 게시 할 수있는 기능을 추가하고 싶습니다. 그래서 제품을 참조하는 Review 클래스가 있습니다. 이제 제품에 대한 모든 리뷰를 나열하는 방법이 필요합니다. 두 가지 가능성이 있습니다.

(ㅏ)

public class Product {
  ...
  public Collection<Review> getReviews() {...}
}

(비)

public class Review {
  ...
  static public Collection<Review> forProduct( Product product ) {...}
}

코드를 살펴보면 (A)를 선택합니다. 정적이 아니며 매개 변수가 필요하지 않습니다. 그러나 (A)는 단일 책임 원칙 (SRP) 및 공개 폐쇄 원칙 (OCP)을 위반하지만 (B)는 그렇지 않다는 것을 알고 있습니다.

  • (SRP) 제품에 대한 리뷰가 수집되는 방식을 변경하려면 제품 클래스를 변경해야합니다. 그러나 Product 클래스를 변경해야하는 이유는 한 가지뿐입니다. 그리고 그것은 확실히 리뷰가 아닙니다. 제품의 제품과 관련이있는 모든 기능을 포장하면 곧 엉망이 될 것입니다.

  • (OCP)이 기능으로 확장하려면 제품 클래스를 변경해야합니다. 이것이 원칙의 '변화에 대한 폐쇄'부분을 위반한다고 생각합니다. 고객의 검토 구현 요청을 받기 전에 제품을 완성 된 것으로 간주하고 "닫았습니다".

더 중요한 것은 SOLID 원칙을 따르거나 더 간단한 인터페이스를 갖는 것입니까?

아니면 내가 여기서 뭔가 잘못하고 있습니까?

결과

와우, 당신의 모든 위대한 답변에 감사드립니다! 공식 답변으로 하나를 선택하기는 어렵습니다.

답변에서 주요 주장을 요약하겠습니다.

  • pro (A) : OCP는 법이 아니며 코드의 가독성도 중요합니다.
  • pro (A) : 엔티티 관계는 탐색 가능해야합니다. 두 클래스 모두이 관계에 대해 알고있을 것입니다.
  • pro (A) + (B) : 둘 다 수행하고 (A)에서 (B)로 위임하여 제품이 다시 변경 될 가능성이 줄어 듭니다.
  • 프로 (C) : 파인더 메소드를 정적이 아닌 3 급 (서비스)에 넣습니다.
  • 콘트라 (B) : 테스트에서 조롱을 방해합니다.

직장 내 대학에서 제공 한 몇 가지 추가 사항 :

  • pro (B) : ORM 프레임 워크는 (B)에 대한 코드를 자동으로 생성 할 수 있습니다.
  • pro (A) : ORM 프레임 워크의 기술적 이유로 파인더가가는 곳과는 독립적으로 "닫힌"엔터티를 변경해야합니다. 어쨌든 항상 SOLID를 고수 할 수는 없습니다.
  • 대조 (C) : 많은 소란 ;-)

결론

현재 프로젝트에 위임과 함께 (A) + (B)를 모두 사용하고 있습니다. 그러나 서비스 지향 환경에서는 (C)를 사용하겠습니다.


2
정적 변수가 아닌 한 모든 것이 멋집니다. 정적 메소드는 테스트하기 쉽고 추적하기 쉽습니다.
Coder

3
왜 ProductsReviews 클래스가 없습니까? 그런 다음 제품과 리뷰는 동일하게 유지됩니다. 아니면 내가 오해 할 수도 있습니다.
ElGringoGrande

2
@ 코더 "정적 방법은 테스트하기 간단합니다", 정말로? 그들은 조롱 할 수 없습니다 . 자세한 내용 은 googletesting.blogspot.com/2008/12/… 를 참조하십시오.
StuperUser

1
@StuperUser : 조롱 할 것이 없습니다. Assert(5 = Math.Abs(-5));
Coder

2
테스트 Abs()는 문제가 아니며, 그것에 의존하는 것을 테스트하는 것입니다. 모의를 사용하기 위해 종속 코드 테스트 (CUT)를 분리하기위한 이음새가 없습니다. 즉, 원자 단위로 테스트 할 수 없으며 모든 테스트는 단위 논리를 테스트하는 통합 테스트가됩니다. 테스트 실패는 CUT 또는 Abs()종속 코드 에있을 수 있으며 단위 테스트의 진단 이점을 제거합니다.
StuperUser 2016 년

답변:


7

더 중요한 것은 SOLID 원칙을 따르거나 더 간단한 인터페이스를 갖는 것입니까?

SOLID에 대한 인터페이스

이들은 상호 배타적이지 않습니다. 인터페이스는 비즈니스 모델의 성격을 비즈니스 모델 용어로 이상적으로 표현해야합니다. SOLID 원칙은 객체 지향 코드 유지 관리 성을 최대화하기위한 Koan입니다. 전자는 비즈니스 모델의 사용 및 조작을 지원하고 후자는 코드 유지 보수를 최적화합니다.

개방 / 폐쇄 원리

"그것을 만지지 마십시오!" 너무 단순한 해석입니다. 그리고 우리가 "계급"을 의미한다고 가정한다면, 반드시 옳은 것은 아닙니다. 오히려 OCP는 코드 동작을 수정하더라도 기존의 작동 코드를 직접 수정하지 않아도되도록 코드를 설계했음을 의미합니다. 또한, 처음부터 코드를 건드리지 않는 것이 기존 인터페이스의 무결성을 유지하는 이상적인 방법입니다. 이것은 OCP의 중요한 추론입니다.

마지막으로 OCP를 기존 설계 품질의 지표로보고 있습니다. 내가 오픈 클래스 (또는 메소드)를 너무 자주 크래킹하거나 / 또는 그렇게하는 확실한 이유가 없다면 이것이 나쁜 디자인을 가지고 있다고 말할 수 있습니다. OO를 코딩하는 방법을 알고 있어야합니다.

최선을 다해 의사 팀이

요구 사항 분석에 따라 두 가지 관점에서 제품-검토 관계를 표현해야한다고 알려주는 경우 그렇게하십시오.

따라서 Wolfgang, 기존 클래스를 수정해야 할 충분한 이유가있을 수 있습니다. 새로운 요구 사항이 주어지면 검토가 이제 제품의 기본 부분 인 경우 제품의 모든 확장에 검토가 필요한 경우 클라이언트 코드를 적절하게 표현하면 제품에 통합하십시오.


1
OCP가 코드의 빠른 규칙이 아니라 좋은 품질을 나타내는 지표라는 점에 주목하십시오. OCP를 제대로 따르기가 어렵거나 불가능한 경우,보다 유연한 재사용을 위해 모델을 리팩터링해야한다는 신호입니다.
CodexArcanum

Product와 Review의 예를 선택하여 Product는 프로젝트의 핵심 엔터티 인 반면 Review는 단순한 추가 기능임을 나타냅니다. 따라서 제품은 이미 존재하고 완성되었으며 검토는 나중에 제공되며 기존 코드 (제품 포함)를 열지 않고 소개해야합니다.
Wolfgang

10

SOLID는 지침이므로 의사 결정을 내리기보다는 의사 결정에 영향을 미칩니다.

정적 메소드를 사용할 때 알아야 할 한 가지는 테스트 가능성에 대한 영향입니다 .


테스트 forProduct(Product product)는 문제가되지 않습니다.

그것에 의존하는 것을 테스트하는 것이 될 것입니다.

응용 프로그램이 실행될 때 정적 메소드가 반드시 존재하기 때문에 mock을 사용하기 위해 종속 코드 테스트 (CUT)를 분리하기위한 이음새가 없습니다.

CUT()호출 하는 메소드를 호출 해 봅시다forProduct()

경우 forProduct()입니다 static당신은 테스트 할 수 없습니다 CUT()원자 단위로하여 모든 테스트는 테스트 단위 로직이 통합 테스트된다.

CUT 대한 테스트에 실패하는 문제에 의해 야기 될 수도 CUT()또는 forProduct()단위 테스트 진단 효과를 제거 (또는 종속 코드 중).

자세한 내용은 다음의 훌륭한 블로그 게시물을 참조하십시오. http://googletesting.blogspot.com/2008/12/static-methods-are-death-to-testability.html


이로 인해 실패한 테스트와이를 둘러싼 모범 사례 및 이점을 포기하는 데 어려움을 겪을 수 있습니다.


1
아주 좋은 지적입니다. 일반적으로, 나는 아닙니다. ;-) 두 개 이상의 클래스를 다루는 단위 테스트 작성에 대해서는 그렇게 엄격하지 않습니다. 그들은 모두 데이터베이스로 내려갑니다. 그래서 나는 비즈니스 객체 또는 파인더를 조롱하지 않을 것입니다.
Wolfgang

+1, 테스트를 위해 적기를 설정해 주셔서 감사합니다! @Wolfgang에 동의합니다. 보통 너무 엄격하지는 않지만 그런 종류의 테스트가 필요할 때 정적 메소드가 정말로 싫어합니다. 일반적으로 정적 메소드가 매개 변수와 너무 많이 상호 작용하거나 다른 정적 메소드와 상호 작용하는 경우 인스턴스 메소드로 만드는 것을 선호합니다.
Adriano Repetti 18시 42 분

이것이 사용되는 언어에 전적으로 의존하지 않습니까? OP는 Java 예제를 사용했지만 문제의 언어를 언급하지 않았으며 태그에 지정된 언어도 없었습니다.
이즈 카타

1
@StuperUser If forProduct() is static you can't test CUT() as an atomic unit and all of your tests become integration tests that test unit logic.-Javascript와 Python 모두 정적 메서드를 재정의 / 조롱 할 수 있다고 생각합니다. 그래도 100 % 확실하지는 않습니다.
이즈 카타

1
@Izkata JS는 동적으로 유형이 지정되어 있으므로 static클로저 및 싱글 톤 패턴으로 시뮬레이션합니다. 파이썬 (특히 stackoverflow.com/questions/893015/… )을 읽으면 상속하고 확장해야합니다. 재정의는 조롱하지 않습니다. 여전히 코드를 원자 단위로 테스트하기위한 이음새가없는 것 같습니다.
StuperUser

4

제품이 제품의 리뷰를 찾기에 적합한 장소라고 생각하면 언제든지 제품에 도움이되는 수업을 제공 할 수 있습니다. 비즈니스에서는 제품 측면을 제외하고는 리뷰에 대해 이야기하지 않기 때문에 알 수 있습니다.

예를 들어, 리뷰 리트리버의 역할을 한 무언가를 주입하고 싶습니다. 아마 인터페이스를 줄 것입니다 IRetrieveReviews. 이것을 제품의 생성자 (Dependency Injection)에 넣을 수 있습니다. 리뷰 검색 방법을 변경하려면 다른 공동 작업자 ( TwitterReviewRetriever또는 하나 AmazonReviewRetriever또는 MultipleSourceReviewRetriever다른 필요한 것)를 주입하여 쉽게 검토 할 수 있습니다 .

둘 다 이제 단일 책임 (제품과 관련된 모든 일을하고 검토를 검색해야 함)을 갖게되며 앞으로는 실제로 제품을 변경하지 않고도 검토와 관련된 제품의 동작을 수정할 수 있습니다 (확장 할 수 있음) A와 ProductWithReviews) 당신은 정말 당신의 SOLID 원칙에 대한 현학적 싶었다, 그러나 이것은 나를 위해 충분한 것입니다 경우.


서비스 / 컴포넌트 지향 소프트웨어에서 매우 일반적인 DAO 패턴처럼 들립니다. 나는이 아이디어가 마음에 든다. 객체 검색은 이러한 객체의 책임이 아님을 나타낸다. 그러나 서비스 지향보다 객체 지향 방식을 선호하기 때문에.
Wolfgang

1
같은 인터페이스를 사용하면 IRetrieveReviews서비스 지향적이지 않으며 리뷰를 얻는 대상, 방법,시기를 결정하지 않습니다. 어쩌면 그것은 이런 것들에 대한 많은 방법을 가진 서비스 일 것입니다. 어쩌면 그 일을하는 수업 일 수도 있습니다. 리포지토리이거나 서버에 대한 HTTP 요청을 수행 할 수 있습니다. 당신은 모른다. 당신은 몰라 야합니다. 그게 요점입니다.
Lunivore

예, 전략 패턴의 구현입니다. 이것은 세 번째 클래스에 대한 논쟁입니다. (A)와 (B)는 이것을 지원하지 않습니다. 파인더는 확실히 ORM을 사용하므로 알고리즘을 대체 할 이유가 없습니다. 내 질문에 이것에 대해 명확하지 않은 경우 죄송합니다.
Wolfgang

3

ProductsReview 클래스가 있습니다. 당신은 검토가 어쨌든 새로운 것을 말한다. 그렇다고 그냥 아무 것도 될 수있는 것은 아닙니다. 변경해야 할 이유는 여전히 하나뿐입니다. 어떤 이유로 든 리뷰를받는 방법을 변경하면 Review 클래스를 변경해야합니다.

맞지 않습니다.

정적 클래스를 Review 클래스에 넣는 이유는 ... 왜? 당신이 어려움을 겪고 있지 않습니까? 그게 문제가 아닙니까?

그럼 하지마 전적으로 책임이 제품 리뷰를받는 수업을 만듭니다. 그런 다음 ProductReviewsByStartRating으로 서브 클래 싱 할 수 있습니다. 또는 하위 클래스로 분류하여 제품 클래스에 대한 리뷰를 얻습니다.


검토 방법을 사용하는 것이 SRP를 위반한다는 데 동의하지 않습니다. 제품에서는 검토 중이 아닙니다. 내 문제는 정적이라는 것이 었습니다. 메소드를 세 번째 클래스로 이동하면 여전히 정적이며 제품 매개 변수가 있습니다.
Wolfgang

따라서 리뷰 클래스에 대한 유일한 책임은 클래스 변경의 유일한 이유는 forProduct 정적 메소드를 변경해야하는지 여부입니다. Review 클래스에는 다른 기능이 없습니까?
ElGringoGrande 19시 24 분

리뷰에는 많은 것들이 있습니다. 그러나 나는 forProduct (Product)가 잘 맞을 것이라고 생각합니다. 리뷰를 찾는 것은 리뷰의 속성과 구조 (어떤 고유 한 식별자, 범위를 변경하는 속성)에 따라 크게 달라집니다. 그러나이 제품은 리뷰에 대해 알지 못합니다.
Wolfgang

3

Product클래스 나 클래스 에 '제품 리뷰 가져 오기'기능을 넣지 않겠습니다 Review...

제품을 검색 할 수있는 곳이 있습니까? GetProductById(int productId)아마도 뭔가 GetProductsByCategory(int categoryId)등등.

마찬가지로 리뷰를 검색 할 수있는 장소가 있어야 GetReviewbyId(int reviewId)합니다 GetReviewsForProduct(int productId).

제품에 대한 리뷰가 수집되는 방식을 변경하려면 제품 클래스를 변경해야합니다.

도메인 클래스에서 데이터 액세스를 분리하는 경우, 당신은 변경할 필요가 없습니다 중 하나 는 리뷰를 수집하는 방식을 변경할 때 도메인 클래스를.


이것이 내가 처리하는 방법이며 게시물이 올 때 까지이 답변이 부족하여 혼란 스럽습니다 (내 생각이 적절한 지 걱정됩니다). 분명히 보고서를 나타내는 클래스 나 제품의 표현이 실제로 다른 것을 검색하는 책임을지지 않아야합니다. 어떤 종류의 데이터 제공 서비스가이를 처리해야합니다.
CodexArcanum

@CodexArcanum 글쎄, 당신은 혼자가 아닙니다. :-)
Eric King

2

패턴과 원칙은 지침이며 돌로 작성된 규칙이 아닙니다. 제 생각 에는 SOLID 원칙을 따르거나 더 간단한 인터페이스를 유지하는 것이 더 나은지에 대한 질문은 아닙니다. 스스로에게 물어보아야 할 것은 대부분의 사람들 이 더 읽기 쉽고 이해하기 쉬운 것입니다. 이것은 종종 도메인에 최대한 가까워 야한다는 것을 의미합니다.

이 경우 나는 때문에 솔루션 (B)를 선호하는 나를 위해 시작 지점이 아닌 검토 제품입니다하지만 상상 은 리뷰를 관리 할 수있는 소프트웨어를 작성하고 있습니다. 이 경우 중심 이 검토이므로 솔루션 (A)가 바람직 할 수 있습니다.

이와 같은 많은 메소드 (클래스 사이의 "연결")가있는 경우 외부에서 모두 제거하고 하나 이상의 새 정적 클래스를 작성하여 구성합니다. 일반적으로 쿼리 또는 저장소 종류로 볼 수 있습니다.


나는 또한 양쪽 끝의 의미론과 어떤 것이 더 강한 지에 대한 아이디어를 생각하여 파인더를 주장 할 것이다. 그래서 내가 (B)를 생각해 냈습니다. 또한 비즈니스 클래스의 "추상화"계층 구조를 만드는 데 도움이됩니다. 제품은 리뷰가 더 높은 단순한 기본 개체였습니다. 따라서 Review는 Product를 참조 할 수 있지만 다른 방법은 아닙니다. 이 방법으로 비즈니스 클래스 간의 참조 사이클을 피할 수 있으며, 이는 내 목록의 다른 문제가 해결 될 것입니다.
Wolfgang

나는 작은 클래스 세트에 대해 두 가지 방법 (A)와 (B)를 제공하는 것이 좋을 것이라고 생각합니다. 이 논리를 쓰는 순간 UI가 너무 자주 알려져 있지 않습니다.
Adriano Repetti 18시 37 분

1

제품은 정적 Review 메소드에 위임 할 수 있습니다.이 경우 자연 위치 (Product.getReviews)에서 편리한 인터페이스를 제공하지만 구현 세부 사항은 Review.getForProduct에 있습니다.

SOLID는 지침이며 간단하고 합리적인 인터페이스를 제공해야합니다. 또는 단순하고 합리적인 인터페이스에서 SOLID를 도출 할 수 있습니다. 코드 내의 종속성 관리에 관한 모든 것입니다. 목표는 마찰을 유발하고 불가피한 변화에 대한 장벽을 만드는 종속성을 최소화하는 것입니다.


나는 이것을 원할지 모르겠다. 이런 식으로, 나는 두 곳에서 좋은 방법을 가지고 있는데, 다른 개발자들은 두 클래스를 모두 볼 필요가 없기 때문에 두 클래스를 모두 볼 필요가 없기 때문입니다. 다른 한편으로, 나는 정적 메소드의 단점과 "폐쇄 된"제품 클래스의 변경 모두를 가질 것 입니다. 대표단이 마찰 문제를 해결하는 데 도움이 될지 확실하지 않습니다. 파인더를 변경해야 할 이유가 있다면 서명이 변경되어 제품이 다시 변경 될 수 있습니다.
Wolfgang

OCP의 핵심은 코드를 변경하지 않고 구현을 대체 할 수 있다는 것입니다. 실제로 이것은 Liskov 대체와 밀접한 관련이 있습니다. 한 구현은 다른 구현으로 대체 가능해야합니다. DatabaseReviews 및 SoapReviews를 IGetReviews.getReviews 및 getReviewsForProducts를 구현하는 다른 클래스로 사용할 수 있습니다. 그런 다음 시스템은 확장을 위해 개방되어 있으며 (검토를받는 방법 변경) 폐쇄를 위해 폐쇄됩니다 (IGetReviews에 대한 종속성이 중단되지 않음). 그것이 종속성 관리의 의미입니다. 귀하의 경우 행동을 변경하려면 코드를 수정해야합니다.
pfries

당신이 묘사하고있는 의존성 역전 원리 (DIP)가 아닙니까?
Wolfgang

그것들은 관련 원칙입니다. 대체 지점은 DIP에 의해 달성됩니다.
pfries

1

나는 대부분의 다른 답변과는 조금 다릅니다. 나는 그렇게 생각 Product하고 Review기본적으로 데이터 전송 객체 (DTO들)입니다. 내 코드에서 DTO / 엔티티가 동작을 피하도록 노력합니다. 그들은 내 모델의 현재 상태를 저장하는 훌륭한 API 일뿐입니다.

OO 및 SOLID에 대해 이야기 할 때 일반적으로 상태를 나타내지 않고 (필수적으로) 질문에 답변하거나 일부 작업을 위임 할 수있는 일종의 서비스를 나타내는 "개체"에 대해 말하는 것입니다. . 예를 들어 :

interface IProductRepository
{
    void SaveNewProduct(IProduct product);
    IProduct GetProductById(ProductId productId);
    bool TryGetProductByName(string name, out IProduct product);
}

interface IProduct
{
    ProductId Id { get; }
    string Name { get; }
}

class ExistingProduct : IProduct
{
    public ProductId Id { get; private set; }
    public string Name { get; private set; }
}

그런 다음 실제 ProductRepository는 메소드 등 ExistingProduct을 반환합니다 GetProductByProductId.

이제 당신은 단일 책임 원칙을 따르고 있습니다 (상속되는 IProduct것은 상태를 유지하는 것이며, 상속하는 IProductRepository것은 데이터 모델을 유지하고 재수 화하는 방법을 아는 것에 책임이 있습니다).

데이터베이스 스키마를 변경하면 DTO 등을 변경하지 않고 리포지토리 구현을 변경할 수 있습니다.

간단히 말해서, 나는 당신의 옵션 중 어느 것도 선택하지 않을 것이라고 생각합니다. :)


0

정적 메소드는 테스트하기가 더 어려울 수 있지만 그렇다고 사용할 수는 없습니다. 테스트 할 필요가 없도록 설정하기 만하면됩니다.

product.GetReviews 및 Review.ForProduct를 모두 한 줄로 만듭니다.

new ReviewService().GetReviews(productID);

ReviewService는 더 복잡한 코드를 모두 포함하고 테스트 가능하도록 설계된 인터페이스를 가지고 있지만 사용자에게 직접 노출되지는 않습니다.

적용 범위가 100 % 여야하는 경우 통합 테스트에서 제품 / 검토 클래스 메소드를 호출하도록하십시오.

클래스 디자인보다는 퍼블릭 API 디자인에 대해 생각한다면 도움이 될 수 있습니다. 그 맥락에서 논리적 그룹화가있는 간단한 인터페이스 만 있으면됩니다. 실제 코드 구조는 개발자에게만 중요합니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.