인터페이스를 행동으로하는 추상 기본 클래스?


14

C # 프로젝트의 클래스 계층을 디자인해야합니다. 기본적으로 클래스 기능은 WinForms 클래스와 유사하므로 WinForms 툴킷을 예로 들어 보겠습니다. (그러나 WinForms 또는 WPF를 사용할 수 없습니다.)

모든 클래스가 제공해야하는 몇 가지 핵심 속성과 기능이 있습니다. 치수, 위치, 색상, 가시성 (true / false), 그리기 방법 등

디자인 조언이 필요합니다. 추상적 인 기본 클래스와 인터페이스는 실제로 유형이 아니지만 동작과 유사한 인터페이스를 사용한 디자인을 사용했습니다. 이것이 좋은 디자인입니까? 그렇지 않은 경우 더 나은 디자인은 무엇입니까?

코드는 다음과 같습니다.

abstract class Control
{
    public int Width { get; set; }

    public int Height { get; set; }

    public int BackColor { get; set; }

    public int X { get; set; }

    public int Y { get; set; }

    public int BorderWidth { get; set; }

    public int BorderColor { get; set; }

    public bool Visible { get; set; }

    public Rectangle ClipRectange { get; protected set; }

    abstract public void Draw();
}

일부 컨트롤에는 다른 컨트롤이 포함될 수 있으며 일부는 (자식으로) 포함될 수 있으므로 이러한 기능에 대해 두 개의 인터페이스를 만들려고합니다.

interface IChild
{
    IContainer Parent { get; set; }
}

internal interface IContainer
{
    void AddChild<T>(T child) where T : IChild;
    void RemoveChild<T>(T child) where T : IChild;
    IChild GetChild(int index);
}

WinForms는 디스플레이 텍스트를 제어하므로 인터페이스에도 적용됩니다.

interface ITextHolder
{
    string Text { get; set; }
    int TextPositionX { get; set; }
    int TextPositionY { get; set; }
    int TextWidth { get; }
    int TextHeight { get; }

    void DrawText();
}

일부 컨트롤은 부모 컨트롤 안에 도킹 할 수 있습니다.

enum Docking
{ 
    None, Left, Right, Top, Bottom, Fill
}

interface IDockable
{
    Docking Dock { get; set; }
}

... 이제 구체적인 클래스를 만들어 봅시다 :

class Panel : Control, IDockable, IContainer, IChild {}
class Label : Control, IDockable, IChild, ITextHolder {}
class Button : Control, IChild, ITextHolder, IDockable {}
class Window : Control, IContainer, IDockable {}

내가 여기서 생각할 수있는 첫 번째 "문제"는 인터페이스가 일단 게시되면 기본적으로 설정되어 있다는 것입니다. 그러나 앞으로 인터페이스를 변경할 필요가 없을 정도로 인터페이스를 양호하게 만들 수 있다고 가정 해 봅시다.

이 디자인에서 또 다른 문제는 이러한 모든 클래스가 인터페이스를 구현해야하며 코드 복제가 빠르게 발생한다는 것입니다. 예를 들어 Label 및 Button에서 DrawText () 메서드는 ITextHolder 인터페이스 또는 자식의 IContainer 관리에서 파생 된 모든 클래스에서 파생됩니다.

이 문제에 대한 나의 해결책은 전용 어댑터에서이 "중복 된"기능을 구현하고 이들에게 호출을 전달하는 것입니다. 따라서 Label과 Button에는 ITextHolder 인터페이스에서 상속 된 메서드 내에서 호출되는 TextHolderAdapter 멤버가 있습니다.

이 디자인은 가상 클래스와 불필요한 "노이즈 코드"로 빠르게 부 풀릴 수있는 기본 클래스의 많은 공통 기능을 갖지 못하게해야한다고 생각합니다. 제어 파생 클래스가 아닌 어댑터를 확장하여 동작 변경을 수행 할 수 있습니다.

나는 이것이 "전략"패턴이라고 생각하며, 그 주제에 대해 수백만 개의 질문과 답변이 있지만,이 디자인에 대해 고려할 사항과 생각할 수있는 결함에 대한 의견을 묻고 자합니다. 내 접근 방식.

향후 요구 사항이 새로운 클래스와 새로운 기능을 요구할 가능성은 거의 100 %라고 덧붙여 야합니다.


이유는 간단에서 상속하지 System.ComponentModel.ComponentSystem.Windows.Forms.Control또는 기존의 다른 기본 클래스의? 왜 자신 만의 제어 계층을 생성하고이 모든 기능을 처음부터 다시 정의해야합니까?
코디 그레이

3
IChild끔찍한 이름처럼 보입니다.
Raynos

@Cody : 먼저, WinForms 또는 WPF 어셈블리를 사용할 수 없습니다. 두 번째로, 제가 묻는 디자인 문제의 예일뿐입니다. 모양이나 동물을 더 쉽게 생각할 수 있다면. 내 수업은 윈폼 컨트롤을하지만 모든면에서와 정확히 그들처럼 비슷한 행동 할 필요가
grapkulec

2
WinForms 또는 WPF 어셈블리를 사용할 수는 없지만 생성 한 사용자 지정 컨트롤 프레임 워크를 모두 사용할 수 있다고 말하는 것은 의미가 없습니다. 이것은 바퀴를 재발 명 하는 심각한 경우이며 가능한 목적으로는 상상할 수 없습니다. 그러나 반드시 필요한 경우 WinForms 컨트롤 의 를 따르십시오 . 그것은이 질문을 거의 쓸모 없게 만듭니다.
코디 그레이

1
@graphkulec 그것이 의견 인 이유입니다 :) 실제로 유용한 피드백을 주면 귀하의 질문에 대답했을 것입니다. 그것은 여전히 ​​끔찍한 이름입니다.)
Raynos

답변:


5

[1] 속성에 가상 "getters"및 "setters"를 추가하십시오.이 기능이 필요하기 때문에 다른 제어 라이브러리를 해킹해야했습니다.

abstract class Control
{
    // internal fields for properties
    protected int _Width;
    protected int _Height;
    protected int _BackColor;
    protected int _X;
    protected int _Y;
    protected bool Visible;

    protected int BorderWidth;
    protected int BorderColor;


    // getters & setters virtual !!!

    public virtual int getWidth(...) { ... }
    public virtual void setWidth(...) { ... }

    public virtual int getHeight(...) { ... }
    public virtual void setHeight(...) { ... }

    public virtual int getBackColor(...) { ... }
    public virtual void setBackColor(...) { ... }

    public virtual int getX(...) { ... }
    public virtual void setX(...) { ... }

    public virtual int getY(...) { ... }
    public virtual void setY(...) { ... }

    public virtual int getBorderWidth(...) { ... }
    public virtual void setBorderWidth(...) { ... }

    public virtual int getBorderColor(...) { ... }
    public virtual void setBorderColor(...) { ... }

    public virtual bool getVisible(...) { ... }
    public virtual void setVisible(...) { ... }

    // properties WITH virtual getters & setters

    public int Width { get getWidth(); set setWidth(value); }

    public int Height { get getHeight(); set setHeight(value); }

    public int BackColor { get getBackColor(); set setBackColor(value); }

    public int X { get getX(); set setX(value); }

    public int Y { get getY(); set setY(value); }

    public int BorderWidth { get getBorderWidth(); set setBorderWidth(value); }

    public int BorderColor { get getBorderColor(); set setBorderColor(value); }

    public bool Visible { get getVisible(); set setVisible(value); }

    // other methods

    public Rectangle ClipRectange { get; protected set; }   
    abstract public void Draw();
} // class Control

/* concrete */ class MyControl: Control
{
    public override bool getVisible(...) { ... }
    public override void setVisible(...) { ... }
} // class MyControl: Control

나는이 제안이 더 "장황"하거나 복잡하다는 것을 알고 있지만, 실제 세계에서는 매우 유용하다.

[2] "IsEnabled"속성을 추가하십시오. "IsReadOnly"와 혼동하지 마십시오 :

abstract class Control
{
    // internal fields for properties
    protected bool _IsEnabled;

    public virtual bool getIsEnabled(...) { ... }
    public virtual void setIsEnabled(...) { ... }

    public bool IsEnabled{ get getIsEnabled(); set setIsEnabled(value); }
} // class Control

당신이 당신의 통제를 보여 주어야하지만 정보를 보여주지 않아야한다는 것을 의미합니다.

[3] "IsReadOnly"속성을 추가하고 "IsEnabled"와 혼동하지 마십시오.

abstract class Control
{
    // internal fields for properties
    protected bool _IsReadOnly;

    public virtual bool getIsReadOnly(...) { ... }
    public virtual void setIsReadOnly(...) { ... }

    public bool IsReadOnly{ get getIsReadOnly(); set setIsReadOnly(value); }
} // class Control

즉, 컨트롤은 정보를 표시 할 수 있지만 사용자가 변경할 수는 없습니다.


기본 클래스의 모든 가상 메소드가 마음에 들지는 않지만 디자인에 대한 내 생각에 두 번째 기회를 줄 것입니다. 그리고 당신은 내가 정말 IsReadOnly 속성이 필요하다는 것을 상기시켰다 :)
grapkulec

@grapkulec 기본 클래스이므로 파생 클래스에 해당 속성이 있지만 동작을 제어하는 ​​메소드는 각 클래스
용도에

mmkey, 나는 당신의 요점을보고 답변 시간을 내 주셔서 감사합니다
grapkulec

1
인터페이스로 "멋진"디자인을 구현하기 시작한 후에는 가상 세터와 때로는 게터가 필요하다는 것을 빨리 알게 되었기 때문에이 답변을 받아 들였습니다. 그렇다고 인터페이스를 전혀 사용하지 않는다는 것을 의미하지는 않습니다. 인터페이스를 실제로 필요한 곳으로 제한했습니다.
grapkulec

3

좋은 질문! 가장 먼저 주목할 점은 draw 메서드에 매개 변수가 없다는 것입니다. 어디에서 그리나요? 인터페이스 또는 매개 변수로 Surface 또는 Graphics 객체를 받아야한다고 생각합니다.

내가 이상하게 여기는 것은 IContainer 인터페이스입니다. 그것은이 GetChild반환 한 아이 그 방법 및 매개 변수가 없습니다 - 어쩌면 컬렉션을 반환해야 또는 인터페이스에 그리기 방법을 이동하는 경우 그 다음이 인터페이스를 구현하고 노출하지 않고 내부 자식 컬렉션을 그릴 수있는 그리기 방법을 가질 수를 .

어쩌면 WPF를 볼 수있는 아이디어가있을 수도 있습니다. 제 생각에는 매우 잘 설계된 프레임 워크입니다. 또한 컴포지션 및 데코레이터 디자인 패턴을 살펴볼 수도 있습니다. 첫 번째는 컨테이너 요소에 유용하고 두 번째는 요소에 기능을 추가하는 데 유용 할 수 있습니다. 나는 그것이 첫 번째 모습에서 얼마나 잘 작동하는지 말할 수 없지만 여기에 내가 생각하는 것이 있습니다 :

중복을 피하기 위해 텍스트 요소와 같은 기본 요소와 같은 것을 가질 수 있습니다. 그런 다음이 기본 요소를 사용하여보다 복잡한 요소를 빌드 할 수 있습니다. 예를 들어, a Border는 자식이 하나 인 요소입니다. Draw메서드 를 호출 하면 테두리와 배경을 그린 다음 테두리 안에 자식을 그립니다. A TextElement만 일부 텍스트를 그립니다. 이제 버튼을 원한다면이 두 가지 기본 요소를 작성하여 (예 : 텍스트를 테두리 안에 넣음) 버튼을 만들 수 있습니다. 여기서 테두리는 데코레이터와 같습니다.

게시물이 다소 길어지고 있으므로 그 아이디어가 흥미로워지면 예를 들어 설명 할 수 있습니다.


1
누락 된 매개 변수는 문제가 아니며 결국 실제 방법의 스케치 일뿐입니다. WPF와 관련하여 아이디어를 기반으로 레이아웃 시스템을 설계 했으므로 데코레이터의 경우 이미 컴포지션 부분이 있다고 생각합니다. 기본 요소에 대한 당신의 생각은 합리적으로 들리며 확실히 시도해보십시오. 감사!
grapkulec

@grapkulec 천만에요! params에 대해-네, 괜찮습니다. 나는 당신의 강도를 올바르게 이해하고 싶었습니다. 나는 당신이 아이디어를 좋아해서 기쁘다. 행운을 빕니다!

2

코드를 잘라 내고 질문의 핵심으로 가십시오. "추상적 인 기본 클래스와 인터페이스를 가진 디자인은 유형이 아니라 동작이 좋은 디자인인지 아닌지에 대한 디자인입니다.", 나는 그 접근법에 아무런 문제가 없다고 말합니다.

실제로 각 인터페이스가 소비 하위 시스템이 예상하는 동작을 정의하는 (내 렌더링 엔진에서) 이러한 접근 방식을 사용했습니다. 예를 들어 IUpdateable, ICollidable, IRenderable, ILoadable, ILoader 등이 있습니다. 명확성을 기하기 위해 이러한 "행동"각각을 "Entity.IUpdateable.cs", "Entity.IRenderable.cs"와 같은 별도의 부분 클래스로 분리하고 필드와 메소드를 가능한 한 독립적으로 유지하려고했습니다.

인터페이스를 사용하여 행동 패턴을 정의하는 접근 방식은 제네릭, 제약 조건 및 공변량 유형 매개 변수가 매우 잘 작동하기 때문에 나와 동의합니다.

이 디자인 방법에 대해 내가 관찰 한 것 중 하나는 다음과 같습니다. 소수 이상의 방법으로 인터페이스를 정의하는 경우 동작을 구현하지 않을 수 있습니다.


문제가 발생했거나 순간에 그러한 디자인을 후회하고 실제로 의도 한 작업을 수행하기 위해 자신의 코드와 싸워야 했습니까? 예를 들어 갑자기 너무 많은 행동 구현을 만들어야한다는 것이 밝혀졌고 방금 말이되지 않았고 "백만 개의 가상으로 기본 클래스를 만들고 지옥에서 상속하고 무시하는 것"이라는 오래된 주제를 고수하고 싶었습니까?
grapkulec
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.