SOLID 원리와 코드 구조


150

최근 취업 면접에서 나는 다양한 원칙의 기본 의미를 제공하는 것 외에는 SOLID 에 관한 질문에 대답 할 수 없었습니다 . 정말 버그가 있습니다. 나는 이틀간의 발굴을 해왔지만 아직 만족스러운 요약을 얻지 못했습니다.

인터뷰 질문은 다음과 같습니다.

SOLID 원칙을 엄격히 준수한다고했던 .Net 프로젝트를 살펴보면 프로젝트 및 코드 구조와 관련하여 무엇을 기대할 수 있습니까?

나는 약간 주위에 under 거리고, 실제로 그 질문에 대답하지 않았고, 폭파했다.

이 질문을 어떻게 더 잘 처리 할 수 ​​있습니까?



확장 가능한 추상 빌딩 블록.
rwong

객체 지향 설계의 SOLID 원칙에 따라 수업은 자연스럽게 작고 잘 구성되며 쉽게 테스트되는 경향이 있습니다. 출처 : docs.asp.net/en/latest/fundamentals/…
WhileTrueSleep

답변:


188

S = 단일 책임 원칙

따라서 잘 구성된 폴더 / 파일 구조 및 개체 계층 구조가 나타납니다. 각 클래스 / 기능의 기능은 그 기능이 매우 명백하다는 이름이 있어야하며 해당 작업을 수행하기위한 논리 만 포함해야합니다.

수천 줄의 코드로 거대한 관리자 클래스를 본다면 단일 책임이 따르지 않았다는 신호일 것입니다.

O = 개방 / 폐쇄 원리

이것은 기본적으로 기존 기능의 수정에 최소한의 영향을 미치거나 수정해야하는 새 클래스를 통해 새로운 기능을 추가해야한다는 아이디어입니다.

객체 상속, 하위 타이핑, 인터페이스 및 추상 클래스를 많이 사용하여 기능의 디자인을 실제 구현과 분리하여 다른 사람들이 다른 버전을 따라 영향을받지 않고 다른 버전을 따라 나아갈 수있게 할 것으로 기대합니다. 기발한.

L = Liskov 대체 원리

이것은 하위 유형을 상위 유형으로 처리하는 기능과 관련이 있습니다. 적절한 상속 된 개체 계층 구조를 구현하는 경우 C #에서 즉시 제공됩니다.

공통 객체를 기본 유형으로 처리하고 하위 유형 자체를 인스턴스화하고 작업하는 대신 기본 / 추상 클래스에서 메소드를 호출하는 코드를 볼 것으로 기대합니다.

I = 인터페이스 분리 원리

이것은 SRP와 유사합니다. 기본적으로, 당신은 인터페이스로 기능의 작은 하위 집합을 정의하고 시스템 (분리 유지하기 위해 그와 함께 작동 예 : FileManager파일을 다루는 하나의 responsibilty이있을 수 있습니다 I / O를,하지만 그건를 구현할 수 IFileReaderIFileWriter이는 읽기에 대한 특정 메소드 정의를 포함 파일 쓰기).

D = 의존성 역전 원리.

이 역시 시스템 분리를 유지하는 것과 관련이 있습니다. 아마 당신은 .NET 종속성 주입 라이브러리의 사용에 대한 경계에있을 것 같은 용액에 사용되는 Unity또는 Ninject같은 ServiceLocator 시스템이나 AutoFacServiceLocator.


36
C #에서 많은 LSP 위반을 보았습니다. 누군가 자신의 특정 하위 유형이 전문화되어 인터페이스를 구현할 필요가 없으며 그 부분에 예외를 던질 필요가있을 때마다 ... 일반적인 주니어 접근법입니다. 인터페이스 구현 및 디자인의 오해 문제를 해결하기 위해
Jimmy Hoffa

2
@JimmyHoffa 이것이 코드 계약 사용을 주장하는 주된 이유 중 하나입니다. 계약을 설계하는 사고 과정을 거치면 사람들이 나쁜 습관에서 벗어나게하는 데 도움이됩니다.
Andy

12
나는 "LSP가 C #에서 나온다"는 것을 좋아하지 않으며 DIP를 의존성 주입 연습과 동일시합니다.
Euphoric 2016 년

3
+1이지만 종속성 반전 <> 종속성 주입. 그것들은 함께 잘 작동하지만 의존성 반전은 단순한 의존성 주입 그 이상입니다. 참조 : 야생에서의 복각
Marjan Venema

3
@Andy : 모든 구현 자 (인스턴스화 할 수있는 클래스)가 테스트되는 인터페이스에 정의 된 단위 테스트도 도움이됩니다.
Marjan Venema 2016 년

17

어디서나 의존성 주입이 가능한 많은 작은 클래스와 인터페이스. 큰 프로젝트에서는 IoC 프레임 워크를 사용하여 모든 작은 개체의 수명을 구성하고 관리하는 데 도움이됩니다. 참조 https://stackoverflow.com/questions/21288/which-net-dependency-injection-frameworks-are-worth-looking-into를

엄격하게 SOLID 원칙을 따르는 큰 .NET 프로젝트가 반드시 모든 사람과 함께 작업하기에 좋은 코드베이스를 의미하지는 않습니다. 면접관이 누구인지에 따라, SOLID가 무엇을 의미하는지 이해하고 /하거나 설계 원칙을 독단적으로 따르는 지 확인하기를 원했을 수도 있습니다.

SOLID가 되려면 다음을 따라야합니다.

S 화롯불 책임 원칙, 당신은 그들 각각이 하나의 일을 여러 개의 작은 클래스를 가지게됩니다 만

O의 .NET에서 일반적으로 또한 I 아래 D를 필요로 의존성 주입으로 구현 펜 폐쇄 원칙, ...

L iskov 대체 원칙은 아마도 하나의 라이너로 c #에서 설명하기가 불가능할 것입니다. 운 좋게도 그것을 해결하는 다른 질문이 있습니다. 예 : https://stackoverflow.com/questions/4428725/can-you-explain-liskov-substitution-principle-with-a-good-c-sharp-example

나는 독방 원리가 오픈 폐쇄 원칙과 함께 작동 nterface. 문자 그대로 따르는 경우 "큰"인터페이스가 아닌 매우 작은 인터페이스를 선호한다는 의미입니다.

D의 높은 수준의 클래스가 낮은 수준의 클래스에 의존해서는 안 ependency 반전 원리는 모두 추상화에 의존해야한다.


SRP는 "한 가지만 수행"을 의미하지 않습니다.
Robert Harvey

13

일상 업무에서 SOLID를 지원 한 상점의 코드베이스에서 볼 수있는 몇 가지 기본 사항 :

  • 많은 작은 코드 파일-.NET에서 모범 사례로 파일 당 하나의 클래스와 작은 모듈 식 클래스 구조를 장려하는 단일 책임 원칙 (Single Responsibility Principle)은 작은 모듈 식 클래스 구조를 포함하는 많은 파일을 볼 것으로 기대합니다.
  • 많은 어댑터 및 복합 패턴-많은 어댑터 패턴 (다른 인터페이스의 기능에 "통과"하여 하나의 인터페이스를 구현하는 클래스)을 사용하여 한 가지 목적을 위해 개발 된 종속성의 연결을 약간 간소화 할 것으로 예상합니다 기능이 필요한 다른 장소. 사용할 파일 이름을 지정하는 수단을 노출하도록 인터페이스가 업데이트되면 콘솔 로거를 파일 로거로 바꾸는 것만 큼 간단한 업데이트는 LSP / ISP / DIP를 위반합니다. 대신 파일 로거 클래스가 추가 멤버를 노출 한 다음 어댑터는 새 항목을 숨겨서 파일 로거를 콘솔 로거처럼 보이게하므로이 모든 것을 결합하는 오브젝트 만 차이점을 알아야합니다.

    마찬가지로 클래스가 기존 인터페이스와 유사한 인터페이스의 종속성을 추가해야하는 경우 객체 (OCP) 변경을 피하기 위해 일반적인 대답은 복합 / 전략 패턴 (종속 인터페이스를 구현하고 다른 여러 요소를 소비하는 클래스)을 구현하는 것입니다. 클래스가 하나, 일부 또는 모든 구현에 호출을 전달할 수 있도록하는 다양한 양의 논리를 가진 해당 인터페이스의 구현).

  • 많은 인터페이스와 ABC-DIP는 반드시 추상화가 필요하며 ISP는 이러한 범위를 좁히도록 권장합니다. 따라서 인터페이스와 추상 기본 클래스가 규칙이며 코드베이스의 공유 종속성 기능을 다루려면 많은 클래스가 필요합니다. 엄격한 SOLID는 모든 것을 주입해야하지만 , 어딘가에 생성해야한다는 것이 분명합니다. 따라서 GUI 양식이 부모에 대한 조치를 수행하여 하나의 부모 양식의 자식으로 만 작성된 경우에는 자식 양식을 새로 작성 할 자격이 없습니다. 부모 내에서 직접 코드에서. 필자는 일반적으로 해당 코드를 자체 메서드로 만들므로 동일한 양식의 두 동작이 창을 열면 메서드를 호출합니다.
  • 많은 프로젝트 –이 모든 것의 핵심은 변화의 범위를 제한하는 것입니다. 변경 사항에는 재 컴파일이 필요합니다 (더 이상 간단한 연습이지만 모바일 환경에 업데이트 배포와 같은 많은 프로세서 및 대역폭이 중요한 작업에서 여전히 중요합니다). 프로젝트에서 하나의 파일을 다시 빌드해야하는 경우 모든 파일이 재생됩니다. 즉, 구현과 동일한 라이브러리에 인터페이스를 배치하면 요점이 없습니다. 인터페이스 정의 자체를 다시 컴파일해야하므로 사용법은 결과 바이너리의 새 위치를 가리켜 야하므로 인터페이스 구현을 변경하면 모든 사용법을 다시 컴파일해야합니다. 따라서 인터페이스를 용도 별도로 유지 구현은 일반적인 사용 영역에 따라 추가로 분리하는 것이 일반적인 모범 사례입니다.
  • "Gang of Four"용어에 많은주의를 기울였습니다-1994 년 책 Design Patterns 에서 식별 된 설계 패턴 은 SOLID가 만들려고하는 바이트 크기의 모듈 식 코드 설계를 강조합니다. 예를 들어 Dependency Inversion Principle과 Open / Closed Principle은이 책에서 식별 된 대부분의 패턴의 핵심입니다. 따라서 저는 SOLID 원칙을 강력하게 준수하는 상점이 Gang of Four의 책에서 용어를 수용하고 "AbcFactory", "XyzRepository", "DefToXyzAdapter와 같은 해당 라인을 따라 기능에 따라 클래스 이름을 지정해야합니다. ","A1Command "등
  • 일반 리포지토리-일반적으로 이해되는 ISP, DIP 및 SRP와 함께 리포지토리는 SOLID 설계에서 거의 보편적으로 사용됩니다. 리포지토리는 검색 / 지속 메커니즘에 대한 특정 지식이 없어도 코드를 사용하여 추상화 된 방식으로 데이터 클래스를 요청할 수 있기 때문입니다. DAO 패턴과 달리이 작업을 수행하는 코드를 한 곳에 배치합니다 (예 : 송장 데이터 클래스가있는 경우 해당 유형의 수화 된 객체를 생성하는 InvoiceDAO도 있습니다). 코드베이스 / 스키마의 모든 데이터 객체 / 테이블).
  • IoC 컨테이너-실제로 의존성 주입의 대부분을 수행하기 위해 IoC 프레임 워크를 사용하지 않기 때문에 이것을 추가하는 것을 망설입니다. 그것은 컨테이너에 모든 것을 던지고 그것을 떨고 주입 된 공장 방법을 통해 필요한 수직 수화 된 의존성을 쏟아내는 신의 물체 반 패턴이됩니다. 구조가 꽤 모 놀리식이되고 등록 정보가있는 프로젝트 (유창한 경우)가 솔루션의 모든 것에 대해 모든 것을 알아야한다는 것을 알 때까지는 훌륭하게 들립니다. 그것은 변화해야 할 많은 이유입니다. 유창하지 않은 경우 (설정 파일을 사용한 늦은 등록) 프로그램의 핵심 부분은 완전히 다른 수의 웜인 "매직 문자열"에 의존합니다.

1
왜 downvotes?
KeithS

나는 이것이 좋은 대답이라고 생각합니다. 이러한 용어가 무엇인지에 대한 많은 블로그 게시물과 비슷하지 않고 사용법과 가치를 보여주는 예제와 설명을 나열했습니다.
Crowie

10

SOLID의 'O'가 어떻게 "도움이없고 이해가 잘되지 않는지"에 대한 Jon Skeet의 설명 으로 혼란을 겪고 Alistair Cockburn의 "보호 된 변형"과 Josh Bloch의 "상속을위한 설계 또는 금지"에 대해 이야기하도록하십시오.

Skeet의 기사에 대한 짧은 요약 (원래 블로그 게시물을 읽지 않고 그의 이름을 삭제하지 않는 것이 좋습니다!) :

  • 대부분의 사람들은 '개방형 원칙'에서 '개방'과 '폐쇄'가 무엇을 의미하는지 알지 못합니다.
  • 일반적인 해석은 다음과 같습니다.
    • 모듈은 항상 구현 상속을 통해 확장되어야합니다.
    • 원래 모듈의 소스 코드는 절대 변경할 수 없습니다.
  • OCP의 기본 의도와 Bertrand Meyer의 원래 공식은 다음과 같습니다.
    • 모듈은 클라이언트가 의존 할 수있는 명확하게 정의 된 인터페이스 ( '인터페이스'의 기술적 의미는 아님)를 가져야 하지만
    • 인터페이스를 중단하지 않고 수행 할 수있는 작업을 확장 할 수 있어야합니다.
  • 그러나 "개방형"및 "폐쇄 형"이라는 단어는 발음하기 쉬운 약어를 만들더라도 문제를 혼동합니다.

OP는 "이 질문을 어떻게 더 잘 처리 할 수 ​​있습니까?"라고 물었습니다. 인터뷰를 진행하는 선임 엔지니어로서, 글 머리 기호 목록을 따돌릴 수있는 사람보다 다양한 코드 디자인 스타일의 장단점에 대해 지능적으로 이야기 할 수있는 후보자에 대해 더 관심이있을 것입니다.

또 다른 좋은 대답은 "글쎄요, 그것은 그것을 얼마나 잘 이해했는지에 달려 있습니다. 그들이 알고있는 모든 것이 SOLID 유행어라면 상속의 남용, 의존성 주입 프레임 워크의 남용, 백만 개의 작은 인터페이스는 없을 것입니다. 제품 관리와 통신하는 데 사용되는 도메인 어휘를 반영합니다. "


6

다양한 시간에 응답 할 수있는 여러 가지 방법이있을 수 있습니다. 그러나 이것이 "SOLID의 의미를 알고 있습니까?" 따라서이 질문에 대답하는 것은 아마도 요점을 맞추고 프로젝트 측면에서 설명하는 것입니다.

따라서 다음을 보게 될 것입니다.

  • 클래스에는 단일 책임이 있습니다 (예 : 고객의 데이터 액세스 클래스는 고객 데이터베이스에서만 고객 데이터를 가져옵니다).
  • 기존 행동에 영향을주지 않고 수업을 쉽게 확장 할 수 있습니다. 추가 기능을 추가하기 위해 속성이나 다른 방법을 수정할 필요가 없습니다.
  • 파생 클래스는 기본 클래스로 대체 될 수 있으며 해당 기본 클래스를 사용하는 함수는 처리하기 위해 기본 클래스를보다 구체적인 유형으로 랩핑 할 필요가 없습니다.
  • 인터페이스는 작고 이해하기 쉽습니다. 클래스가 인터페이스를 사용하는 경우 작업을 수행하기 위해 여러 메소드에 의존 할 필요가 없습니다.
  • 높은 수준의 구현이 구체적인 낮은 수준의 구현에 구체적으로 의존하지 않도록 코드는 충분히 추상화됩니다. 고급 코드에 영향을 미치지 않고 저수준 구현을 전환 할 수 있어야합니다. 예를 들어, 웹 응용 프로그램 기반의 SQL 데이터 액세스 계층을 나머지 응용 프로그램에 영향을주지 않고 전환 할 수 있습니다.

4

나는 그것이 어려운 인터뷰 질문이라고 생각하지만 이것은 훌륭한 질문입니다.

SOLID 원칙은 실제로 클래스와 인터페이스 및 이들이 서로 관련되는 방식을 결정합니다.

이 질문은 실제로 파일 과 더 관련이 있으며 반드시 클래스는 아닙니다.

내가 줄 간단한 관찰 또는 답변은 일반적으로 인터페이스 만 포함하는 파일을 보게 될 것이며, 종종 관례는 대문자 I로 시작한다는 것입니다. 그 외에도 파일에 중복 코드 (특히 모듈, 응용 프로그램 또는 라이브러리 내)가 없으며 해당 코드는 모듈, 응용 프로그램 또는 라이브러리 사이의 특정 경계에서 신중하게 공유됩니다.

Robert Martin 은 Booch 방법을 사용하여 객체 지향 C ++ 응용 프로그램 설계 (Chesion , Closure 및 Reusability 섹션 참조) 및 Clean Code 에서 C ++ 영역에서이 주제에 대해 설명합니다 .


.NET 코더 IME는 일반적으로 "파일 당 1 개의 클래스"규칙을 따르며 폴더 / 네임 스페이스 구조를 미러링합니다. Visual Studio IDE는 두 가지 방법을 모두 권장하며 ReSharper와 같은 다양한 플러그인이이를 실행할 수 있습니다. 따라서 클래스 / 인터페이스 구조를 미러링하는 프로젝트 / 파일 구조가 보일 것으로 예상됩니다.
KeithS
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.