어떤 OO 디자인을 사용해야합니까 (디자인 패턴이 있습니까)?


11

'Bar / Club'(음료 / 사회적 장소)을 나타내는 두 가지 개체가 있습니다.

한 시나리오에서 막대 이름, 주소, 거리, 슬로건이 필요합니다.

다른 시나리오에서는 바 이름, 주소, 웹 사이트 URL, 로고가 필요합니다

그래서 같은 것을 나타내지 만 다른 필드를 가진 두 개의 객체가 있습니다.

불변의 객체를 사용하고 싶습니다. 그래서 모든 필드는 constructor에서 설정됩니다 .

하나의 옵션은 두 개의 생성자를 사용하고 다른 필드는 null입니다.

class Bar {
     private final String name;
     private final Distance distance;
     private final Url url;

     public Bar(String name, Distance distance){
          this.name = name;
          this.distance = distance;
          this.url = null;
     }

     public Bar(String name, Url url){
          this.name = name;
          this.distance = null;
          this.url = url;
     }

     // getters
}

getter를 사용할 때 null 검사를해야하므로 이것을 좋아하지 않습니다.

실제 예제에서 첫 번째 시나리오에는 3 개의 필드가 있고 두 번째 시나리오에는 약 10 개의 필드가 있으므로 두 개의 생성자 가 있으면 고통 이 될 것 입니다. 필드를 null로 선언 해야하는 필드의 수는 객체가 사용 중일 때 ' Bar어떤 필드를 사용하고 어떤 필드가 null인지, 그렇지 않은 필드를 알지 못합니다.

다른 옵션이 있습니까?

두 클래스 BarPreviewBar?

어떤 유형의 상속 / 인터페이스?

다른 멋진 것이 있습니까?


29
와우, 당신은 실제로 Bar식별자로서의 합법적 인 사용을 생각해 냈습니다 !
메이슨 휠러

1
일부 속성을 공유하는 경우 하나의 옵션은 기본 클래스를 구현하는 것입니다.
Yusubov

1
나는 그런 생각을하지 않았다. Bar / Foo dog parlor에 어떤 종류의 코드라도 작성하면 실제로 혼란 스러울 수 있습니다.
Erik Reppen


4
@ gnat 사람들은 어떻게 추측합니까. 귀하의 링크 인용에서 : You should only ask practical, answerable questions based on actual problems that you face.그것은 정확히 무슨 일이 일어나고 있는지
Blundell

답변:


9

내 생각:

도메인에 표시되는 '바'에는 이름, 주소, URL, 로고, 슬로건 및 거리와 같은 장소에 필요할 수있는 모든 항목이 있습니다 (요청자의 위치에서 추측합니다). 따라서 도메인에는 나중에 데이터가 사용될 위치에 관계없이 하나의 막대에 대한 권한있는 데이터 소스 인 "Bar"클래스가 하나 있어야합니다. 이 클래스는 변경 가능해야 바의 데이터를 변경하고 필요할 때 저장할 수 있습니다.

그러나이 Bar 객체의 데이터가 필요한 두 곳이 있으며 둘 다 하위 집합 만 필요하며 해당 데이터를 변경하지 않으려는 경우가 있습니다. 일반적인 대답은 "데이터 전송 개체"또는 DTO입니다. 불변의 프로퍼티 게터를 포함한 POJO (plain ol 'Java object) 이러한 DTO는 기본 Bar 도메인 오브젝트에서 "toScenario1DTO ()"및 "toScenario2DTO ()"메소드를 호출하여 생성 할 수 있습니다. 결과는 수화 된 DTO입니다 (즉, 길고 복잡한 생성자를 한곳에서 사용하면 됨).

데이터를 주 도메인 클래스로 다시 보내야하는 경우 (업데이트; 실제 세계의 현재 상태를 반영하기 위해 필요에 따라 데이터를 변경할 수없는 경우 데이터의 요점은 무엇입니까?) DTO를 사용하거나 새로운 가변 DTO를 사용하고 "updateFromDto ()"메소드를 사용하여 Bar 클래스에 다시 전달하십시오.

편집 : 예를 제공하십시오.

public class Bar {
     private String name;
     private Address address; 
     private Distance distance;
     private Url url;
     private Image logo;
     private string Slogan;

     public OnlineBarDto ToOnlineDto()
     {
         return new OnlineBarDto(name, address, url, logo);
     }

     public PhysicalBarDto ToPhysicalDto()
     {
         return new PhysicalBarDto(name, address, distance, slogan);
     }

     public void UpdateFromDto(PhysicalBarDto dto)
     {
         //validation logic here, or mixed into assignments

         name = dto.Name;
         address = dto.Address;
         distance = dto.Distance;
         slogan = dto.Slogan;
     }

     public void UpdateFromDto(OnlineBarDto dto)
     {
         //Validate DTO fields before performing assignments

         name = dto.Name;
         address = dto.Address;
         url= dto.Url;
         logo = dto.Logo;
     }

     // getters/setters - As necessary within the model and data access layers;
     // other classes can update the model using DTOs, forcing validation.
}

public class PhysicalBarDto
{
     public final String Name;
     public final Address Address;
     public final Distance Distance;
     public final String Slogan;

     public PhysicalBarDto(string Name, Address address, Distance distance, string slogan) 
     { //set instance fields using parameter fields; you know the drill }
}

public class OnlineBarDto
{
     public final String Name;
     public final Address Address;
     public final Image Logo;
     public final Url Url;

     public OnlineBarDto(string Name, Address address, Url url, Image logo) 
     { //ditto }
}

Address, Distance 및 Url 클래스는 변경 불가능하거나 DTO에서 사용될 때 변경 불가능한 클래스로 대체해야합니다.


약어의 DTO가 약자 나는 당신이 당신의 말을 실제로 예제로 넣을 수있는 것을 얻지 못합니다. 참고 데이터는 서버에서 제공 되므로이 클래스의 두 형식 중 하나라도 '수화'되면 필드를 변경할 필요가 없으며 UI에 표시하는 데 사용됩니다
Blundell

1
DTO는 "데이터 전송 개체"를 나타내며 실제 도메인 레이어를 UI에 노출시키지 않고 "리치"도메인 레이어에서 UI와 같은 상위 레이어로 데이터를 이동하는 데 사용되는 매우 간단한 구조의 데이터 클래스를 나타냅니다 (변경 허용). DTO를 변경할 필요가없는 한 UI에 영향을주지 않으면 서 도메인으로 이동해야합니다.
KeithS

변경 가능성 은 수정이나 지속성과 관련이 없습니다.

4
@JarrodRoberson-농담 해? 클래스가 변경 불가능한 경우 (인스턴스화 후 내부에서 변경할 수없는 경우) 데이터 계층에서 데이터를 변경하는 유일한 방법은 다른 멤버와 동일한 레코드 (동일한 PK)를 나타내는 새 인스턴스를 구성하는 것입니다. 새로운 인스턴스를 생성하는 "돌연변이 (mutating)"방법을 사용하면 더 쉽게 만들 수 있지만 여전히 수정 및 지속성에 영향을 미칩니다.
KeithS

1
@JarrodRoberson 커뮤니티의 말을 듣습니다. 당신은 잘못. . 사실 반에서 우리는 보드 주위의 학교 교육 기본적인 OO가 필요하다고이 전체 응답 쇼 코멘트 -이 .. 역겨운 것
데이비드 한 Cowden

5

속성의 하위 집합 만 신경 쓰고 서로 혼동되지 않도록하려면 두 개의 인터페이스를 만들고이를 사용하여 기본 개체와 대화하십시오.


1
당신은 말할 수 있지만 Bar클래스 를 사용하여 예를 들어 줄 수 있습니다
Blundell

3

빌더 패턴 (그것 또는 무언가 가까이) 여기에 사용 될 수 있습니다.

불변의 객체를 갖는 것은 훌륭한 일이지만 실제로는 Reflection in Java를 사용하면 실제로 안전한 것은 없습니다 ;-).


HawaiianPizzaBuilder필요한 값이 하드 코딩되어 있기 때문에 작동 방식을 볼 수 있습니다 . 그러나 값을 검색하여 생성자에 전달한 경우이 패턴을 어떻게 사용할 수 있습니까? 는 HawaiianPizzaBuilder여전히이있는 모든 게터 것 SpicyPizzaBuilder널 (null)이 가능하므로이있다. 이것을 @Jarrods와 결합하지 않는 한 Null Object Pattern. 코드 예제를 Bar통해 요점을 알 수 있습니다.
Blundell

+1 나는 이런 경우에 빌더를 사용하고, 매력처럼 작동한다-이것을 원할 때 null 대신 합리적인 기본값을 설정하는 것에 국한되지는 않는다
gnat

3

여기서 중요한 점은 "바"의 정의 와 하나 또는 다른 컨텍스트에서 이를 사용하는 방법 의 차이점 입니다.

Bar는 실제 세계 (또는 게임과 같은 인공 세계)의 단일 엔티티이며 하나의 객체 인스턴스 만 표시해야합니다. 나중에 코드 세그먼트에서 해당 인스턴스를 만들지 않고 구성 파일 또는 데이터베이스에서 인스턴스를로드하면 더 분명합니다.

(매우 난해 해짐에 따라 : 각 Bar 인스턴스는 프로그램이 실행될 때이를 나타내는 오브젝트와 다른 라이프 사이클을 갖습니다. 해당 인스턴스를 작성하는 소스 코드가 있어도 설명 된 바와 같이 Bar 엔티티가 존재한다는 것을 의미합니다. "소스 코드에서 휴면 상태에 있고 해당 코드가 실제로 메모리에 생성 될 때"깨어 나면 "...)

오랫동안 시작해서 미안하지만, 이것이 내 요점을 분명히하기를 바랍니다. 필요한 모든 속성을 가진 하나의 Bar 클래스 와 각 Bar 엔티티를 나타내는 하나의 Bar 인스턴스가 있습니다. 이것은 코드에서 정확 하며 다른 컨텍스트에서 동일한 인스턴스를 보는 방법 과 무관 합니다 .

후자는 필요한 액세스 메소드 (getName (), getURL (), getDistance ())를 포함하는 두 개의 서로 다른 인터페이스 로 표시 될 수 있으며 Bar 클래스는 둘 다 구현해야합니다. (그리고 아마도 "거리"는 "위치"로 바뀌고 getDistance ()는 다른 위치에서 계산됩니다 :-))

그러나 생성은 Bar 엔티티를위한 것이며 해당 엔티티를 사용하려는 방식이 아닙니다. 하나의 생성자, 모든 필드.

편집 : 코드를 작성할 수 있습니다! :-)

public interface Place {
  String getName();
  Address getAddress();
}

public interface WebPlace extends Place {
   URL getUrl();
   Image getLogo();
}

public interface PhysicalPlace extends Place {
  Double getDistance();
  Slogon getSlogon();
}

public class Bar implements WebPlace, PhysicalPlace {
  private final String name;
  private final Address address;
  private final URL url;
  private final Image logo;
  private final Double distance;
  private final Slogon slogon;

  public Bar(String name, Address address, URL url, Image logo, Double distance, Slogon slogon) {
    this.name = name;
    this.address = address;
    this.url = url;
    this.logo = logo;
    this.distance = distance;
    this.slogon = slogon;
  }

  public String getName() { return name; }
  public Address getAddress() { return address; }
  public Double getDistance() { return distance; }
  public Slogon getSlogon() { return slogon; }
  public URL getUrl() { return url; }
  public Image getLogo() { return logo; } 
}

1

적절한 패턴

찾고있는 것을 가장 일반적으로로 지칭합니다 Null Object Pattern. 이름이 마음에 들지 않으면 Undefined Value Pattern같은 의미 론적 다른 레이블 이라고 할 수 있습니다 . 때때로이 패턴을라고 Poison Pill Pattern합니다.

이러한 모든 경우에, 개체 교체는 나에 대해 서 Default Value대신 null. It doesn't replace the semantic ofbut makes it easier to work with the data model in a more predictable way because지금해야 null` 결코 유효한 상태로 수 없습니다.

지정된 클래스의 특수 인스턴스를 예약하여 달리 null옵션을로 표시하는 패턴 Default Value입니다. 이 방법으로에 대해 확인할 필요가 없으며 null알려진 NullObject인스턴스 에 대해 ID를 확인할 수 있습니다 . 걱정할 필요없이 메소드 등을 안전하게 호출 할 수 있습니다 NullPointerExceptions.

이렇게하면 null과제를 대표 NullObject인스턴스로 바꾸고 완료됩니다.

적절한 객체 지향 분석

이 방법으로 Interface다형성에 대한 공통점 을 가질 수 있으며 인터페이스의 특정 구현에서 데이터의 부재에 대해 걱정할 필요가 없습니다. 따라서 일부 Bar는 웹에 존재하지 않을 수 있으며 일부는 생성 당시 위치 데이터가 없을 수 있습니다. Null Object Patter이를 통해 각 marker데이터 에 대해 기본값을 제공 할 수 있습니다. 즉, 모든 데이터를 확인하지 않고도 동일한 정보를 제공하는 데이터가 여기에 제공되지 않은 NullPointerException것입니다.

적절한 객체 지향 디자인

먼저 abstract구현 BarClub공유 하는 모든 속성의 수퍼 세트 인 구현이 있습니다.

class abstract Establishment 
{
     private final String name;
     private final Distance distance;
     private final Url url;

     public Bar(final String name, final Distance distance, final Url url)
     {
          this.name = name;
          this.distance = distance;
          this.url = url;
     }

     public Bar(final String name, final Distance distance)
     {
          this(name, distance, Url.UNKOWN_VALUE);
     }

     public Bar(final String name, final Url url)
     {
          this(name, Distance.UNKNOWN_VALUE, url);
     }

     // other code
}

그럼 당신은 이것의 서브 클래스를 구현할 수있는 Establishment클래스를하고 각각에 필요한 단지 특정 일 추가 BarClub다른 적용되지 않습니다 클래스를.

고집

이러한 자리 표시 자 개체는 올바르게 구성된 경우 특별한 처리없이 데이터베이스에 투명하게 저장할 수 있습니다.

미래의 증거

나중에 Inversion of Control / Dependency Injection bandwagon으로 넘어 가기로 결정한 경우이 패턴을 사용하면 이러한 마커 객체를 쉽게 주입 할 수 있습니다.


0

문제는 두 시나리오 중 하나에서 막대를 모델링하지 않는다는 것입니다 (그리고 두 가지 다른 문제, 객체 등을 모델링하고 있습니다). 클래스 바가 보이면 음료, 메뉴, 좌석 및 개체와 관련된 기능이 없을 것입니다. 객체 동작이 표시되면 시설에 대한 정보를 모델링하는 것입니다. Bar는 현재 당신이 사용하고있는 것이지만, 그들이 구현하는 본질적인 행동은 아닙니다. (다른 맥락에서 : 결혼을 모델링하는 경우 두 가지 인스턴스 변수 Person wife; Person husband; 아내는 현재 해당 객체에 현재 역할을 부여하지만 해당 객체는 여전히 Person입니다). 나는 이런 식으로 할 것입니다 :

class EstablishmentInformation {
     private final String name;

     public EstablishmentInformation(String name){
          this.name = name;
     }

     // getters
}

class EstablishmentLocationInformation {
    EstablishmentInformation establishmentInformation;
     private final Distance distance;

     public EstablishmentLocationInformation (String name, Distance distance){
          this.establishmentInformation = new EstablishmentInformation(name)
          this.distance = distance;
     }
}

class EstablishmentWebSiteInformation {
    EstablishmentInformation establishmentInformation;
     private final Url url;

     public EstablishmentWebSiteInformation(String name, Url url){
          this.establishmentInformation = new EstablishmentInformation(name)
          this.url = url;
     }
}

-1

과도하게 복잡 할 필요는 없습니다. 서로 다른 두 종류의 물체가 필요하십니까? 두 개의 수업을 만듭니다.

class OnlineBar {
     private final String name;
     private final Url url;
     public OnlineBar(String name, Url url){
          this.name = name;
          this.url = url;
     }

     // ...
}
class PhysicalBar {
     private final String name;
     private final Distance distance;
     public PhysicalBar(String name, Distance distance){
          this.name = name;
          this.distance = distance;
     }
     //...
}

동일하게 작업해야하는 경우 인터페이스 추가 또는 리플렉션 사용을 고려하십시오.


@David : 아, 아뇨. 그들은 하나의 전체 데이터 멤버를 공통으로 가지고 있습니다. 비상 사태!
DeadMG

일반적인 인터페이스가 없으면 이 솔루션 에는 다형성 이 없으며, 이러한 잘못된 클래스는이 잘못된 설계 결정으로 다른 클래스로 대체 될 수 없습니다. 속성에 동일한 수퍼 세트가없는 것은 아니며 기본적으로 이러한 속성 중 일부는 null입니다. 기억 null은 속성이 아니라 데이터없음을 의미 합니다.

1
@DeadMG 이것은 공유 값을 부모 객체로 그룹화하는 아이디어에 대한 연습 일 가능성이 높습니다. 해당 컨텍스트에서 제안한 솔루션은 완전한 신뢰를 얻지 못합니다.
David Cowden

영업 이익은 지정하지 않는 모든 대체 가능성에 대한 필요성을. 내가 말했듯이 인터페이스를 추가하거나 원하는 경우 리플렉션을 사용할 수 있습니다.
DeadMG

@DeadMG 그러나 리플렉션은 하나의 환경을 사용하여 크로스 플랫폼 모바일 앱을 작성하는 것과 같습니다. 작동 할 수도 있지만 올바르지 않습니다 . 리플렉션을 사용하여 메소드를 호출 할 때의 벌금은 일반 메소드 호출보다 2-50 배 느립니다. 반영은 완치가 아닙니다 ..
David Cowden

-1

이 유형의 문제가있는 사람에 대한 나의 대답은 문제 를 관리 가능한 단계로 나누는 것 입니다.

  1. 먼저 두 클래스를 생성 BarOne하고 BarTwo(또는 둘 다 호출 Bar하지만, 다른 패키지)
  2. 객체를 별도의 클래스로 사용하기 시작하면 코드 중복에 대해 걱정할 필요가 없습니다. 서로 교차 할 때 (중복 방법) 통지
  3. 당신은 그들이 결코 관련이 없다는 것을 알 수 있습니다, 그래서 당신 은 그들에게 실제로bar 문제가되는 클래스의 이름을 지금 나타내는 것을 바꾸지 않는지 스스로에게 물어봐야합니다
  4. 공통 필드 또는 동작을 찾은 경우 공통 동작 interface또는superclass
  5. 당신이이 있으면 interface또는 superclass당신은을 만들 수 있습니다 builder또는 factory구현 객체를 검색 / 생성

(4와 5는이 질문에 대한 다른 답변에 관한 것입니다)


-2

기본 클래스 (예 : 이름주소 가있는 Location )가 필요합니다 . 이제 BarBarPreview라는 두 클래스 가 기본 클래스 Location을 확장합니다 . 각 클래스에서 수퍼 클래스 공통 변수를 초기화 한 다음 고유 변수를 초기화하십시오.

public class Location {
    protected final String name;
    protected final String address:

    public Location (String locName, String locAddress) {
    name = locName;
    address = locAddress
    }

}

public class Bar extends Location {
    private final int dist;
    private final String slogan;

    public Bar(String barName, String barAddress,
               int distance, String barSlogan) {
    super(locName, locAddress);
    dist = distance;
    slogan = barSlogan;
    }
}

BarPreview 클래스와 유사합니다.

밤에 더 잘 수면 내 코드 의 모든 위치 인스턴스 를 AnythingYouThinkWouldBeAnAppropriateNameForAThingThatABarExtendsSuchAsFoodServicePlace -ffs로 바꾸십시오 .


OP는 인스턴스가 불변 인 것을 원합니다. 즉, 모든 것이 있어야 함을 의미합니다 final.

1
이것은 작동 할 수 있습니다 final. @ David 필드 가 필요합니다 . Barextend Location그 이해가되지 않습니다하지만. 아마도 Bar extends BaseBar그리고 BarPreview extends BaseBar이러한 이름은 정말 더 우아한 뭔가를 기대했다,하지만 하나 너무 좋은 소리하지 않습니다
Blundell은

@JarrodRoberson 나는 단지 그를 위해 그것을 스케치하고 있습니다. 단지 불변이 될 수 있도록 final을 추가하십시오. OP의 질문에 대한 근본적인 문제는 기본 클래스와 기본 클래스를 확장하는 두 개의 개별 클래스를 갖는 방법을 모른다는 것입니다. 나는 단순히 그것을 자세히 설명하고 있습니다.
David Cowden

@Blundell 세계에서 무엇에 대해 이야기하고 있습니까? 있다 위치 . 내 위치를 BaseBar로 바꾸면 정확히 같은 것입니다. 한 클래스가 다른 클래스를 확장 할 때 클래스가 확장되는 이름을 Base [ClassThatWillExtend]로 지정할 필요가 없다는 것을 알고 있습니까?
David Cowden

1
@DavidCowden, 다른 모든 답변 아래에 댓글로 답변을 광고하는 것을 그만 둘 수 있습니까?
marktani
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.