Lớp cơ sở trừu tượng với giao diện là hành vi?


14

Tôi cần thiết kế một hệ thống phân cấp lớp cho dự án C # của mình. Về cơ bản, các chức năng của lớp tương tự như các lớp WinForms, vì vậy hãy lấy bộ công cụ WinForms làm ví dụ. (Tuy nhiên, tôi không thể sử dụng WinForms hoặc WPF.)

Có một số tính chất và chức năng cốt lõi mà mỗi lớp cần cung cấp. Kích thước, vị trí, màu sắc, khả năng hiển thị (đúng / sai), phương pháp Vẽ, v.v.

Tôi cần lời khuyên thiết kế Tôi đã sử dụng một thiết kế với lớp cơ sở trừu tượng và các giao diện không thực sự kiểu nhưng giống các hành vi hơn. Đây có phải là một thiết kế tốt? Nếu không, những gì sẽ là một thiết kế tốt hơn.

Mã trông như thế này:

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();
}

Một số Điều khiển có thể chứa các Điều khiển khác, một số Điều khiển chỉ có thể được chứa (khi còn nhỏ) vì vậy tôi đang nghĩ đến việc tạo hai giao diện cho các chức năng này:

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 điều khiển văn bản hiển thị để điều này cũng đi vào giao diện:

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

    void DrawText();
}

Một số Điều khiển có thể được neo trong Kiểm soát cha mẹ của chúng vì vậy:

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

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

... và bây giờ hãy tạo một số lớp cụ thể:

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

"Vấn đề" đầu tiên tôi có thể nghĩ đến ở đây là các giao diện về cơ bản được thiết lập một khi chúng được xuất bản. Nhưng hãy giả sử rằng tôi sẽ có thể làm cho giao diện của mình đủ tốt để tránh phải thay đổi chúng trong tương lai.

Một vấn đề khác tôi thấy trong thiết kế này là mọi lớp trong số này sẽ cần phải thực hiện các giao diện và việc sao chép mã sẽ nhanh chóng xảy ra. Ví dụ, trong Nhãn và Nút, phương thức DrawText () xuất phát từ giao diện ITextHolder hoặc trong mọi lớp có nguồn gốc từ quản lý trẻ em IContainer.

Giải pháp của tôi cho vấn đề này là triển khai các chức năng "trùng lặp" này trong các bộ điều hợp chuyên dụng và chuyển tiếp cuộc gọi đến chúng. Vì vậy, cả Nhãn và Nút sẽ có một thành viên TextHolderAd CHƯƠNG sẽ được gọi bên trong các phương thức được kế thừa từ giao diện ITextHolder.

Tôi nghĩ rằng thiết kế này sẽ bảo vệ tôi khỏi phải có nhiều chức năng phổ biến trong lớp cơ sở, thứ có thể nhanh chóng bị phồng lên với các phương thức ảo và "mã nhiễu" không cần thiết. Thay đổi hành vi sẽ được thực hiện bằng cách mở rộng các bộ điều hợp và không phải các lớp có nguồn gốc Kiểm soát.

Tôi nghĩ rằng đây được gọi là mẫu "Chiến lược" và mặc dù có hàng triệu câu hỏi và câu trả lời về chủ đề đó, tôi muốn hỏi bạn ý kiến ​​của bạn về những gì tôi xem xét cho thiết kế này và những sai sót bạn có thể nghĩ đến cách tiếp cận của tôi.

Tôi nên nói thêm rằng có khả năng gần như 100% rằng các yêu cầu trong tương lai sẽ yêu cầu các lớp mới và các chức năng mới.


Tại sao không chỉ đơn giản là kế thừa từ System.ComponentModel.Componenthoặc System.Windows.Forms.Controlhoặc bất kỳ của các lớp cơ sở khác hiện có? Tại sao bạn cần tạo phân cấp điều khiển của riêng mình và xác định lại tất cả các chức năng này từ đầu?
Cody Grey

3
IChildcó vẻ như một cái tên kinh khủng
Raynos

@Cody: đầu tiên, tôi không thể sử dụng các hội đồng WinForms hoặc WPF, thứ hai - thôi nào, đây chỉ là một ví dụ về vấn đề thiết kế mà tôi đang hỏi về. Nếu nó dễ dàng hơn cho bạn nghĩ hình dạng hoặc động vật. các lớp của tôi cần hành xử tương tự như các điều khiển WinForms nhưng không phải theo mọi cách và không chính xác như chúng
grapkulec

2
Sẽ không có ý nghĩa gì khi nói rằng bạn không thể sử dụng các hội đồng WinForms hoặc WPF, nhưng bạn sẽ có thể sử dụng bất kỳ khung kiểm soát tùy chỉnh nào mà bạn sẽ tạo. Đây là một trường hợp nghiêm trọng về việc phát minh lại bánh xe, và tôi không thể tưởng tượng được mục đích có thể là gì. Nhưng nếu bạn hoàn toàn phải, tại sao không làm theo ví dụ về các điều khiển WinForms? Điều đó làm cho câu hỏi này khá nhiều lỗi thời.
Cody Grey

1
@graphkulec đó là lý do tại sao đó là một nhận xét :) Nếu tôi có phản hồi hữu ích thực sự để cung cấp, tôi sẽ trả lời câu hỏi của bạn. Nó vẫn là một cái tên khủng khiếp;)
Raynos

Câu trả lời:


5

[1] Thêm "getters" & "setters" ảo vào tài sản của bạn, tôi đã phải hack một thư viện điều khiển khác, vì tôi cần tính năng này:

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

Tôi biết đề xuất này là "dài dòng" hoặc phức tạp hơn, nhưng, nó rất hữu ích trong thế giới thực.

[2] Thêm thuộc tính "IsEnables", đừng nhầm lẫn với "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

Có nghĩa là bạn có thể phải thể hiện sự kiểm soát của mình, nhưng không hiển thị bất kỳ thông tin nào.

[3] Thêm thuộc tính "IsReadOnly", đừng nhầm lẫn với "IsEnables":

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

Điều đó có nghĩa là điều khiển có thể hiển thị thông tin, nhưng người dùng không thể thay đổi.


mặc dù tôi không thích tất cả các phương thức ảo này trong lớp cơ sở, tôi sẽ cho chúng cơ hội thứ hai trong suy nghĩ của tôi về thiết kế. và bạn đã nhắc nhở tôi rằng tôi thực sự cần tài sản IsReadOnly :)
grapkulec

@grapkulec Vì nó là lớp cơ sở, bạn phải xác định rằng các lớp dẫn xuất sẽ có các thuộc tính đó, nhưng, các phương thức kiểm soát hành vi có thể được thay đổi theo từng lớp trong lớp ;-)
umlcat

mmkey, tôi thấy quan điểm của bạn và cảm ơn bạn đã dành thời gian trả lời
grapkulec

1
Tôi đã chấp nhận điều này như một câu trả lời vì một khi tôi bắt đầu thực hiện thiết kế "tuyệt vời" của mình với các giao diện, tôi nhanh chóng thấy mình cần setters ảo và đôi khi là cả getters. Điều đó không có nghĩa là tôi hoàn toàn không sử dụng giao diện. Tôi chỉ giới hạn việc sử dụng chúng ở những nơi thực sự cần thiết.
grapkulec

3

Câu hỏi hay! Điều đầu tiên tôi nhận thấy là phương thức vẽ không có bất kỳ tham số nào, nó vẽ ở đâu? Tôi nghĩ rằng nó sẽ nhận được một số đối tượng Surface hoặc Graphics làm tham số (tất nhiên là giao diện).

Một điều nữa tôi thấy kỳ lạ là giao diện IContainer. Nó có GetChildphương thức trả về một con duy nhất và không có tham số - có thể nó sẽ trả về một tập hợp hoặc nếu bạn di chuyển phương thức Draw sang một giao diện thì nó có thể thực hiện giao diện này và có một phương thức vẽ có thể vẽ bộ sưu tập con bên trong của nó mà không để lộ nó .

Có lẽ đối với các ý tưởng bạn cũng có thể nhìn vào WPF - đó là một khung được thiết kế rất tốt theo ý kiến ​​của tôi. Ngoài ra, bạn có thể xem các mẫu thiết kế Thành phần và Trang trí. Cái đầu tiên có thể hữu ích cho các phần tử container và cái thứ hai để thêm chức năng cho các phần tử. Tôi không thể nói nó sẽ hoạt động tốt như thế nào ngay từ cái nhìn đầu tiên, nhưng đây là những gì tôi nghĩ:

Để tránh trùng lặp, bạn có thể có một cái gì đó giống như các yếu tố nguyên thủy - như yếu tố văn bản. Sau đó, bạn có thể xây dựng các yếu tố phức tạp hơn bằng cách sử dụng các nguyên thủy này. Ví dụ, a Borderlà một phần tử có một con. Khi bạn gọi Drawphương thức của nó, nó vẽ một đường viền và một nền và sau đó vẽ con của nó bên trong đường viền. A TextElementchỉ vẽ một số văn bản. Bây giờ nếu bạn muốn có một nút, bạn có thể tạo một nút bằng cách kết hợp hai nguyên thủy đó, tức là đặt văn bản vào Đường viền. Ở đây, Border là một cái gì đó giống như một trang trí.

Bài đăng đang trở nên khá dài vì vậy hãy cho tôi biết nếu bạn thấy ý tưởng đó thú vị và tôi có thể đưa ra một số ví dụ hoặc giải thích thêm.


1
thiếu params không phải là một vấn đề, nó chỉ là một bản phác thảo của các phương thức thực sự. Về WPF tôi đã thiết kế hệ thống bố trí dựa trên ý tưởng của họ vì vậy tôi nghĩ rằng tôi đã có một phần sáng tác, vì đối với các nhà trang trí tôi sẽ phải suy nghĩ về nó. ý tưởng của bạn về các yếu tố nguyên thủy nghe có vẻ hợp lý và tôi chắc chắn sẽ thử. cảm ơn!
grapkulec

@grapkulec Chào mừng bạn! Về thông số - vâng, không sao, tôi chỉ muốn chắc chắn rằng tôi hiểu chính xác tình trạng của bạn. Tôi rất vui vì bạn thích ý tưởng này. Chúc may mắn!

2

Cắt mã và đi vào cốt lõi của câu hỏi của bạn "Thiết kế của tôi có lớp cơ sở trừu tượng và giao diện không phải là kiểu nhưng nhiều hành vi có phải là thiết kế tốt hay không.", Tôi nói không có gì sai với cách tiếp cận đó.

Thực tế tôi đã sử dụng một cách tiếp cận như vậy (trong công cụ kết xuất của mình) trong đó mỗi giao diện xác định hành vi mà hệ thống con tiêu thụ dự kiến. Ví dụ: IUpdatizable, ICollidable, IRenderable, ILoadable, ILoader, v.v. Để rõ ràng, tôi cũng tách từng "hành vi" này thành các lớp riêng biệt như "Entity.IUpdatizable.cs", "Entity.IRenderable.cs" và cố gắng giữ các trường và phương thức độc lập nhất có thể.

Cách tiếp cận của việc sử dụng các giao diện để xác định các mẫu hành vi cũng đồng ý với tôi bởi vì các tham số loại biến đổi chung, ràng buộc và co (ntra) phối hợp rất tốt với nhau.

Một trong những điều tôi quan sát được về phương pháp thiết kế này là: Nếu bạn thấy mình xác định một giao diện với nhiều hơn một số phương thức, có lẽ bạn không thực hiện một hành vi.


bạn có gặp phải bất kỳ vấn đề nào hay trong bất kỳ lúc nào bạn đang hối hận về thiết kế như vậy và phải chiến đấu với mã của chính mình để thực sự hoàn thành công việc? chẳng hạn như đột nhiên hóa ra bạn cần phải tạo ra rất nhiều triển khai hành vi đến mức nó chẳng có ý nghĩa gì và bạn ước gì mình chỉ gắn bó với chủ đề cũ "tạo ra lớp cơ sở với hàng triệu ảo và chỉ kế thừa và ghi đè lên nó"?
grapkulec
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.