8

OOP 및 학습 패턴을 처음 접했기 때문에 Factory Method를 시험해 볼 간단한 코드를 작성 했으므로 다른 하위 유형을 추가하려는 경우를 제외하고는 모두 잘된 것처럼 보입니다. 다음 코드는 지금까지의 : 요구 사항이 나이> 70 일 때를위한 하위 클래스 Pensioner을 포함 나중에 변경자바의 매개 변수화 된 팩토리 메소드를 확장합니다.

public interface Person { 
    public String getDescription(); 
} 

public class Adult implements Person { 
    @Override 
    public String getDescription() { 
    return "I am an ADULT"; 
    } 
} 

public class Child implements Person { 
    @Override 
    public String getDescription() { 
    return "I am a CHILD"; 
    } 
} 

public class PersonFactory { 
    public Person create(int age) { 
    if (age < 18) return new Child(); 
    return new Adult(); 
    } 
} 

public class ClientA { 
    public static void main(String[] args) { 
    PersonFactory personFactory = new PersonFactory(); 
    Person person; 
    person = personFactory.create(80); 
    System.out.println(person.getDescription()); 
    } 
} 

경우 중 하나에 내가 가진 것 :

create()에 선 if (age > 70) return new Pensioner(); 추가 메서드는 반드시 Open-Closed 원칙을 어기는 PersonFactory 클래스에서? 또는 The Gang Of Four Design Patterns에서 제안한 것처럼 매개 변수화 된 팩토리 메서드를 재정의하여 작성자가 생성 한 제품을 선택적으로 확장합니다. 이 경우 그 새로운 클래스를 작성하는 의미 생각 :

public class PersonFactoryWithPensioner extends PersonFactory { 
    @Override 
    public Person create(int age) { 
    if (age > 70) return new Pensioner(); 
    return super.create(age); 
    } 
} 

이 이제 PersonFactory 지금 것이다 전화를 모든 클라이언트 중 하나를 대신 PersonFactoryWithPensioner를 사용하도록 변경 될 수 있음을 의미하거나 그 새를 받아 들여야 고객은 PersonFactoryWithPensioner으로 전화를 걸 수 있습니다. ClientA은 나이가> 70 일 때만 Adult 오브젝트를 수신합니다. 나중에 다른 서브 클래스 인 경우 나중에 더 심각해집니다. Infant이 추가됩니다. 새 클라이언트가 Infant, Child, Adult 또는 Pensioner의 개체를 수신하도록하려면 새 클래스 PersonFactoryWithInfantPersonFactoryWithPensioner까지 확장해야합니다. 이건 옳지 않아, 내가 GoF가 제안한 것을 오해 한 것 같다.

내 질문은 : 변경하지 않고 이전 클라이언트에 반환 할 수있는 새로운 하위 형식을 추가하고 PersonFactory 코드를 변경하여 OCP를 손상시키지 않고 새 하위 유형을 포함 할 수 있습니까?

사과를 올바르게 게시하지 않은 경우 여기에 질문을 게시 한 것은 처음입니다. 비슷한 문제에 대한 이전 답변을 살펴 보았지만 문제를 해결하지 못했습니다.

+0

당신이 읽었습니까 : http://softwareengineering.stackexchange.com/questions/302780/does-the-factory-pattern-violate-the-open-closed-principle – jaudo

+0

나는 닫힌 폐쇄 원리를 깨는 것이 길이라고 생각합니다. 현재의 구현은 자식 또는 어른 생성을 지원하고 if 문을 추가하여 메서드를 변경하는 것이 훨씬 쉽고 깨끗 해지고 팩토리를 확장하는 여러 클래스를 만드는 것을 지원하므로 갈 것입니다. –

+0

이것은 팩토리 메서드 패턴이 아닙니다. 이 예는 일반적으로 단순 공장이라고합니다. GoF 패턴이 아닙니다. – jaco0646

답변

1

질문은 "클라이언트가 코드 수정없이 연금 수령인 오브젝트를 만들 것을 기대합니까?"입니다. 그렇다면 "닫힌"규칙을 위반하고 공장 코드를 업데이트해야합니다. 그렇지 않다면 새 팩토리를 만들어야하며 클라이언트가이를 사용할 것입니다.

2

규칙이 깨지는 경우가 있으므로 규칙을 깨끗하고 단순하게 유지하기 위해 규칙을 삭제하십시오. 각 유형의 사람에 대해 여러 팩토리 클래스를 작성하는 오버 헤드는 필자의 견해로는 전체 팩토리 메소드 목적을 손상시킵니다. 닫힌 원리를 깨면 단일 클래스를 사용하여 임의의 사람 유형을 만들 수 있습니다.

public Person create(int age) { 
    if (age < 4) return new Infant(); 
    if (age < 18) return new Child(); 
    if (age < 70) return new Adult(); 
    return new Pensioner(); 
    } 
+0

언급 한 바와 같이,이 수정은 "Closed"원칙을 깨뜨릴 것입니다. – jaudo

+0

ok 닫힌 원칙이 무엇인지 조사해야합니다. 감사합니다. @julaudo –

+0

OC가 깨지기는하지만 이것은 전적으로 정당하다고 생각하기 때문에 upvoted했습니다. 그렇게하려면 - 내 자신의 대답을 대부분의 요구 사항에 대한 over-engineered보고, @ alayor 함께 동일하게 간다 – tucuxi

2

OCP가 모든 메소드 또는 클래스를 수정하지 않는다고 생각합니다.

그러나 수정을해야 할 경우 코드를 다시 수정할 필요가 없도록 수정해야한다고 제안합니다.

나중에 PersonFactory을 수정해야 할 수도 있습니다. Factory 클래스를 새로 만들면 PersonFactory 유형의 개체를 만들 수 있습니다. 이것은 과 같은 것으로 보이지만 과도하게 설계된 솔루션입니다.

또 다른 가능한 해결책은 PersonFactory이 규칙을 일부 동적 소스에서로드하는 것입니다 (예 : JSON 형식을 사용하여 파일에이 규칙 저장). 그런 다음 리플렉션을 사용하여 동적으로 객체를 만듭니다. 이 같은

뭔가 :

private static JSONObject RULES; 
    static { 
     RULES= JSON.parse(rulesEngine.load()); 
    } 
    public class PersonFactory { 
     public Person create(int age) { 
     String personToCreate = RULES.get(age); 
     Constructor<?> ctor = Class.forName(personToCreate).getConstructor(); 
     return (Person) ctor.newInstance(); 
     } 
    } 

json으로 규칙은 다음과 같이 뭔가 될 것이다 :

{ 
    "1":"Child.class", 
    "2":"Child.class", 
    ..., 
    "17":"Child.class", 
    "18":"Adult.class", 
    ..., 
    "69":"Adult.class", 
    "70":"Pensioner.class" 
} 

당신이 OCP 원칙을 깰하지 않습니다 이쪽으로.

+0

메서드를 호출 할 때마다 규칙을 다시로드 할 제안합니다. 나는 영업 사원이 규칙에 대한 실행 시간 수정에 관심이 있는지 의심 스럽다. 정적 속성을 통해 한 번로드하고 파싱하면 충분합니다. – tucuxi

+0

예, 런타임에로드하는 것이 좋지 않을 수 있습니다. 그러나 요점은 다르다. 요점은 당신이 그 규칙들을 동적으로 바꿀 수 있다는 것이다. – alayor

+1

"PersonFactory를 최신 버전으로 업데이트"에서 "PersonFactory 규칙 파일을 최신 버전으로 업데이트"로 변경했습니다. – tucuxi

2

개폐식 원리는 명심해야합니다. 그러나 공장에서는 잘 작동하지 않습니다. @alayor의 대답 마찬가지로

PersonFactory pf = new PersonFactory(); 
    // Java 8 lambdas are great! 
    pf.register((age) -> age < 18 ? new Child() : null); 
    pf.register((age) -> age >= 18 ? new Adult() : null); 

    System.out.println(pf.create(10).getDescription());  

, 공장의 논리를 수정할 필요 또는 것을 방지 할 수있는 유일한 방법 : 정렬의-작품은 레지스트리에 공장을 도는 다음은, 하나 개 옵션 공장을 완전히 대체하고 모든 사람들이 새로운 버전을 사용하게하려면 ... 공장이 다른 곳에서 그 논리를 얻는 것입니다. @alayor는 설정 파일에서 가져옵니다. 나는 그것을 초기화의 일부로 factory에 추가 할 것을 제안한다. (팩토리 생성자에서도 마찬가지다; 예를 들어 public PersonFactory(PersonCreator ... rules)으로 바꿀 수있다.)

전체 코드 : 여기

interface PersonCreator { 
    Person create(int age); 
} 

class PersonFactory { 
    private List<PersonCreator> pcs = new ArrayList<>(); 

    public void register(PersonCreator pc) { 
     pcs.add(pc); 
    } 

    public Person create(int age) { 
     for (PersonCreator pc : pcs) { 
      Person p = pc.create(age); 
      if (p != null) { 
       return p; 
      } 
     } 
     return null; 
    } 
} 

interface Person { 
    public String getDescription(); 
} 

class Adult implements Person { 
    @Override 
    public String getDescription() { 
     return "I am an ADULT"; 
    } 
} 

class Child implements Person { 
    @Override 
    public String getDescription() { 
     return "I am a CHILD"; 
    } 
} 

public class Main { 
    public static void main(String[] args) { 
     PersonFactory pf = new PersonFactory(); 
     // Java 8 lambdas are great! 
     pf.register((age) -> age < 18 ? new Child() : null); 
     pf.register((age) -> age >= 18 ? new Adult() : null); 

     System.out.println(pf.create(10).getDescription());    
    } 
} 
+0

Downvoter - 이유를 언급하는 관심? – tucuxi

+0

나는 downvoter가 아니다; 그러나 OCP는 GoF 공장 패턴과 완벽하게 작동합니다. 이 스레드 (및 기타 여러 가지)의 문제는 주어진 예제가 GoF 팩토리 패턴이 아니라는 것입니다. 주어진 예제는 일반적으로 OCP를 위반하는 Simple Factory라고 불리지 만 Simple Factory는 더 유명한 GoF 패턴과 관련이 없습니다. – jaco0646

+0

의존성 주입이나 레지스트리가 대신 사용되지 않는 한, 실제로 건물을 짓는 데 필요한 생성자와의 연결을 피할 수있는 공장을 어떻게 보지 못했습니다. 새 클래스를 추가하면 항상 공장의 어딘가에서 새 생성자 호출을해야합니다. GoF 예제는 생성자를 호출하므로 매우 확장 성이 없습니다. 사실 AFAIK는 공장의 주요 목표로이 확장 불가능 지점을 단일 위치에 캡슐화하는 것입니다. – tucuxi

1

모든 답변, 동적 규칙의 어떤 제안 개폐 원리를 위반 사실에 있습니다. 이 원칙은 "이미 작성된 코드를 변경하지 말고"이미 사용중인 코드의 결과를 변경하지 말아야합니다. 즉, 클라이언트가 성인 또는 아동의 두 가지 결과 만 얻을 수 있다고 기대하면 함수 또는 동적 규칙 집합으로 하드 코딩하여 세 번째 가능성을 제공하는 것이 개폐식 원칙을 위반하는 것입니다.

그러나 귀하의 질문으로 돌아가서 - 나는 그것이 달려 있다고 말할 것입니다. 원칙과 패턴은 멋지고 재미 있으며 모든 것이지만 실제 일상에서 항상 큰 그림을보고 특정 규칙을 적용할지 여부를 결정해야합니다. 그들을 돌로 쓰여진 것이 아닌 힌트로 간주하십시오.

코드가 다소 닫혀 있다면 PersonFactory를 호출 할 때마다 제어권을 갖게되고 변경 사항은 소프트웨어의 수명주기에서 정상적인 것입니다. 이전에 만든 코드를 조금도 변경하지 않은 채 실제 프로젝트에 참여한 적이 없습니다. 실제로 우리는 매일 그것을하고 있습니다 :)

다른 것은 제 3 자 클라이언트 (예 : 공개 API)의 알 수없는 번호에 의해 귀하의 코드가 사용될 때입니다. 그럼 뭔가를 깨뜨리지 않도록 조심해야하지만 기존의 방법에 새로운 논리를 제시해야합니다 (여기서 Person의 새로운 개념을 추가 할 때) 완벽하게 받아 들일 수 있습니다. 변경 사항을 깨뜨리는 경우 변경된 코드를 이전 버전과 함께 새로 추가하거나 업그레이드 된 버전을 추가하는 것을 고려하십시오. (나중에 코드를 10000 버전으로 유지하기를 원하지 않을 경우 이전 버전을 향후 사용할 수 없도록 계획 할 수도 있습니다.)

몇 가지 문제를 피하는 데 도움이되는 다른 OOP 조각에 대해서도 기억하십시오. 귀하의 예에서 성인, 자녀 및 연금 수령자 모두 훌륭한 Person 인터페이스를 구현합니다. 따라서 Adult 및 Child 구현 만 알고있는 모든 코드는 Pensioner 값을 사용하는 데 문제가 없어야합니다. 모두가 Person을 구현하기위한 것이므로 코드는 Pensioner를 Person으로 취급해야합니다.