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를,하지만 그건를 구현할 수 IFileReader및 IFileWriter이는 읽기에 대한 특정 메소드 정의를 포함 파일 쓰기).

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.