공개 폐쇄 원칙의 이점을 활용하십니까?


12

OCP (Open-Closed Principal)는 개체가 확장을 위해 열려 있지만 수정을 위해 닫혀 있어야한다고 명시하고 있습니다. 나는 그것을 이해하고 SRP와 함께 사용하여 한 가지만 수행하는 클래스를 만듭니다. 그리고 일부 하위 클래스에서 확장되거나 재정의 될 수있는 메서드로 모든 동작 컨트롤을 추출 할 수있는 많은 작은 메서드를 만들려고합니다. 따라서 종속성 주입 및 구성, 이벤트, 위임 등을 통해 확장 점이 많은 클래스가 생깁니다.

다음과 같이 간단하고 확장 가능한 클래스를 고려하십시오.

class PaycheckCalculator {
    // ...
    protected decimal GetOvertimeFactor() { return 2.0M; }
}

예를 들어 OvertimeFactor1.5로 변경 되었다고 가정 하겠습니다. 위의 클래스는 확장되도록 설계되었으므로 쉽게 서브 클래스 화하고 다른을 반환 할 수 있습니다 OvertimeFactor.

그러나 ... 클래스가 확장 및 OCP를 준수하도록 설계 되었음에도 불구하고 문제의 메소드를 서브 클래 싱 및 오버 라이딩 한 다음 IoC 컨테이너에서 객체를 다시 배선하는 대신 해당 단일 메소드를 수정합니다.

결과적으로 OCP가 달성하려는 일부를 위반했습니다. 위가 조금 더 쉽기 때문에 게으른 느낌입니다. OCP를 오해하고 있습니까? 정말로 다른 것을해야합니까? OCP의 이점을 다르게 활용합니까?

업데이트 : 답변을 기반 으로이 구성 된 예제는 여러 가지 이유로 열악한 것으로 보입니다. 이 예제의 주요 목적은 재정의 될 때 내부 또는 개인 코드 를 변경할 필요없이 공용 메서드 의 동작을 변경할 수있는 메서드를 제공하여 클래스가 확장되도록 설계되었음을 보여주었습니다 . 그래도 OCP를 오해했습니다.

답변:


10

기본 클래스를 수정하는 경우 실제로 닫히지 않았습니다.

도서관을 세상에 공개 한 상황을 생각해보십시오. 초과 근무 요인을 1.5로 수정하여 기본 클래스의 동작을 변경하면 클래스가 닫혔다 고 가정하여 코드를 사용하는 모든 사람들을 위반 한 것입니다.

실제로 클래스를 닫았지만 열려면 대체 소스 (구성 파일 일 수도 있음)에서 초과 근무 요인을 검색하거나 재정의 할 수있는 가상 방법을 증명해야합니까?

클래스가 실제로 닫히면 변경 후 테스트 사례가 실패하지 않으며 (모든 테스트 사례에 100 % 적용 범위가 있다고 가정) 확인하는 테스트 사례가 있다고 가정합니다 GetOvertimeFactor() == 2.0M.

오버 엔지니어

그러나이 개방적인 원칙을 논리적 결론에 따르지 말고 처음부터 모든 것을 구성 할 수 있습니다 (엔지니어링 이상). 현재 필요한 비트 만 정의하십시오.

닫힌 원칙은 객체를 다시 엔지니어링하는 것을 방해하지 않습니다. 현재 정의 된 공용 인터페이스를 객체로 변경할 수 없습니다 ( 보호 된 구성원 은 공용 인터페이스의 일부 임). 이전 기능이 손상되지 않는 한 더 많은 기능을 추가 할 수 있습니다.


"폐쇄 된 원칙으로 인해 객체를 다시 엔지니어링 할 수 없습니다." 사실, 그것은 않습니다 . 공개 폐쇄 원칙이 처음 제안 된 책 또는 "OCP"약어를 소개 한 기사 를 읽으면 "아무도 소스 코드를 변경할 수 없습니다"라는 메시지가 표시됩니다 (버그 제외) 수정).
Rogério

@ Rogério : 사실 일 수도 있습니다 (1988 년). 그러나 현재 정의 (OO가 대중화되었을 때 1990 년에 대중화 됨)는 일관된 공용 인터페이스를 유지하는 것입니다. During the 1990s, the open/closed principle became popularly redefined to refer to the use of abstracted interfaces, where the implementations can be changed and multiple implementations could be created and polymorphically substituted for each other. en.wikipedia.org/wiki/Open/closed_principle
Martin York

Wikipedia 참조 주셔서 감사합니다. 그러나 "현재"정의가 여전히 유형 (클래스 또는 인터페이스) 상속에 의존하기 때문에 실제로 다르다는 확신이 없습니다. 그리고 내가 언급 한 "소스 코드 변경 없음"인용문은 "현재 정의"와 일치하는 Robert Martin의 OCP 1996 기사에서 나온 것입니다. 개인적으로, 공개 마감 원칙은 지금까지 잊혀 졌을 것입니다. Martin은 분명히 마케팅 가치가 높은 약어를 제공하지 않았습니다. 원칙 자체는 구식이며 유해합니다. IMO.
Rogério

3

따라서 Open Closed Principle은 특히 YAGNI 와 동시에 적용하려고 할 때 반드시 필요합니다 . 동시에 둘 다 어떻게 고수합니까? 3규칙을 적용하십시오 . 처음 변경하면 직접 변경하십시오. 그리고 두 번째도. 세 번째로, 그 변화를 추상화해야 할 때입니다.

또 다른 접근 방법은 "한 번만 바보입니다."입니다. 변경해야 할 경우 향후 해당 변경으로부터 보호하기 위해 OCP를 적용 하십시오 . 나는 초과 근무 비율을 바꾸는 것이 새로운 이야기라는 것을 제안하기 위해 거의 갈 것입니다. "급여 관리자로서 적용 가능한 노동법을 준수 할 수 있도록 초과 근무 율을 변경하고 싶습니다." 이제 초과 근무 비율을 변경하는 새로운 UI와이를 저장하는 방법이 있으며 GetOvertimeFactor ()는 해당 저장소에 초과 근무 비율을 묻습니다.


2

게시 한 예에서 초과 근무 요인은 변수 또는 상수 여야합니다. * (자바 예제)

class PaycheckCalculator {
   float overtimeFactor;

   protected float setOvertimeFactor(float overtimeFactor) {
      this.overtimeFactor = overtimeFactor;
   }

   protected float getOvertimeFactor() {
      return overtimeFactor;
   }
}

또는

class PaycheckCalculator {
   public static final float OVERTIME_FACTOR = 1.5f;
}

그런 다음 클래스를 확장 할 때 요소를 설정하거나 재정의하십시오. "매직 넘버"는 한 번만 나타나야합니다. 이것은 OCP와 DRY (Do n't Repeat Yourself) 스타일에 훨씬 가깝습니다. 첫 번째 방법을 사용하는 경우 다른 요소에 대해 완전히 새로운 클래스를 만들 필요가 없으며 하나의 관용구에서 상수 만 변경하면되므로 두 번째 장소.

여러 유형의 계산기가있는 경우 첫 번째를 사용하고 각기 다른 상수 값이 필요합니다. 책임 체인 패턴이 그 예이며 일반적으로 상속 된 유형을 사용하여 구현됩니다. 인터페이스 (예 :) 만 볼 수있는 객체는 인터페이스를 getOvertimeFactor()사용하여 필요한 모든 정보를 가져오고 하위 유형은 제공 할 실제 정보에 대해 걱정합니다.

두 번째는 상수가 변경되지 않지만 여러 위치에서 사용되는 경우에 유용합니다. 하나의 상수를 변경하는 것은 (아마도 그렇지 않은 경우) 어디서나 설정하거나 속성 파일에서 가져 오는 것보다 훨씬 쉽습니다.

공개 폐쇄 원칙은 인터페이스를 변경하지 않고 유지하기 위해 기존 개체를 수정하지 않는 것이 아닙니다. 클래스와 약간 다른 동작이 필요하거나 특정 사례에 대한 기능이 추가 된 경우 확장 및 재정의하십시오. 그러나 클래스 자체의 요구 사항이 변경되면 (요소 변경과 같이) 클래스를 변경해야합니다. 거대한 클래스 계층 구조에는 아무런 의미가 없으며 대부분 사용되지 않습니다.


이것은 코드 변경이 아니라 데이터 변경입니다. 초과 근무 율은 하드 코딩되어서는 안됩니다.
Jim C

Get과 Set이 거꾸로있는 것 같습니다.
메이슨 휠러

으악! 테스트 했어야합니다 ...
Michael K

2

나는 당신의 모범이 OCP의 훌륭한 표현이라고 생각하지 않습니다. 규칙이 실제로 의미하는 바는 다음과 같습니다.

기능을 추가하려면 하나의 클래스 만 추가해야하며 다른 클래스 (구성 파일은 가능)를 수정할 필요가 없습니다.

아래의 구현이 잘못되었습니다. 게임을 추가 할 때마다 GamePlayer 클래스를 수정해야합니다.

class GamePlayer
{
   public void PlayGame(string game)
   {
      switch(game)
      {
          case "Poker":
              PlayPoker();
              break;

          case "Gin": 
              PlayGin();
              break;

          ...
      }
   }

   ...
}

GamePlayer 클래스를 수정할 필요가 없습니다

class GamePlayer
{
    ...

    public void PlayGame(string game)
    {
        Game g = GameFactory.GetByName(game); 
        g.Play();   
    }

    ...
}

이제 GameFactory도 OCP를 준수한다고 가정하면 다른 게임을 추가하고 싶을 때 클래스에서 상속받은 새 클래스를 빌드하면 Game모든 것이 제대로 작동합니다.

첫 번째와 같은 모든 클래스는 수년간의 "확장"이후에 종종 만들어지고 원래 버전에서 올바르게 리팩터링되지 않습니다 (또는 더 나쁜 것은 여러 클래스가 있어야하는 것은 하나의 큰 클래스입니다).

제공 한 예는 OCP-ish입니다. 제 생각에는 초과 근무 율 변경을 처리하는 올바른 방법은 기록이 유지되는 데이터베이스에서 데이터를 재 처리 할 수 ​​있다는 것입니다. 코드는 항상 조회에서 적절한 값을로드하기 때문에 수정을 위해 닫혀 있어야합니다.

실제 사례로 필자의 예제 변형을 사용했으며 공개 폐쇄 원칙이 실제로 빛을 발합니다. 기능은 쉽게 추가 할 수 있습니다. 추상적 인 기본 클래스에서 파생되어야하고 "공장"이 자동으로 선택하고 "플레이어"는 공장이 어떤 구체적인 구현을 수행하든 상관하지 않기 때문입니다.


1

이 특정 예에서는 "매직 값"이라고합니다. 본질적으로 시간이 지남에 따라 변경되거나 변경되지 않을 수있는 하드 코딩 된 값입니다. 나는 당신이 일반적으로 표현하는 수수께끼를 다루려고 노력할 것입니다. 그러나 이것은 서브 클래스를 만드는 것이 클래스의 값을 변경하는 것보다 더 많은 일을하는 유형의 예입니다.

클래스 계층 구조에서 동작을 너무 일찍 지정했을 가능성이 높습니다.

우리가 있다고 가정 해 봅시다 PaycheckCalculator. 은 OvertimeFactor더 이상 가능성이 직원에 대한 정보의 오프 키가 될 것이다. 시간당 직원은 초과 근무 보너스를 누릴 수 있지만 급여를받는 직원은 아무 것도받지 않습니다. 그럼에도 불구하고 일부 급여 직원은 작업중인 계약으로 인해 곧바로 시간을 얻습니다. 알려진 알려진 유료 클래스 클래스가 있다고 판단 할 수 있으며 이것이 논리를 구축하는 방법입니다.

기본 PaycheckCalculator클래스 에서는 추상화하고 원하는 메소드를 지정하십시오. 핵심 계산은 동일하며 특정 요인이 다르게 계산되는 것입니다. 귀하는 HourlyPaycheckCalculator다음 구현하는 것이 getOvertimeFactor방법을하고 경우에 따라 1.5 또는 2.0을 반환합니다. 귀하는 StraightTimePaycheckCalculator를 구현하는 것이 getOvertimeFactor1.0를 반환 할 수 있습니다. 마지막으로 세 번째 구현은 0을 반환하도록 NoOvertimePaycheckCalculator구현하는 것 getOvertimeFactor입니다.

핵심은 확장하려는 기본 클래스의 동작 만 설명하는 것입니다. 전체 알고리즘의 일부 또는 특정 값의 세부 사항은 서브 클래스로 채워집니다. 기본 값을 포함했다는 사실은 getOvertimeFactor의도 한대로 클래스를 확장하지 않고 한 줄로 빠르고 쉽게 "수정"하게합니다. 또한 수업 연장과 관련된 노력이 있다는 사실을 강조합니다. 응용 프로그램의 클래스 계층 구조를 이해하는 데에도 노력이 있습니다. 서브 클래스 작성 필요성을 최소화하면서 필요한 유연성을 제공하는 방식으로 클래스를 설계하려고합니다.

생각할만한 음식 : 클래스가 OvertimeFactor예제 와 같은 특정 데이터 요소를 캡슐화 할 때 다른 소스에서 해당 정보를 가져 오는 방법이 필요할 수 있습니다. 예를 들어, 속성 파일 (이것은 Java처럼 보임) 또는 데이터베이스는 값을 보유 PaycheckCalculator하며 데이터 액세스 오브젝트를 사용하여 값을 가져옵니다. 이를 통해 올바른 사람들이 코드를 다시 쓰지 않고도 시스템의 동작을 변경할 수 있습니다.

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