Làm thế nào để tranh luận chống lại điều này, tư duy hoàn toàn công khai về thiết kế lớp đối tượng kinh doanh


9

Chúng tôi đang thực hiện nhiều thử nghiệm đơn vị và tái cấu trúc các đối tượng kinh doanh của mình và dường như tôi có những ý kiến rất khác nhau về thiết kế lớp so với các đồng nghiệp khác.

Một lớp ví dụ mà tôi không phải là người hâm mộ của:

public class Foo
{
    private string field1;
    private string field2;
    private string field3;
    private string field4;
    private string field5;

    public Foo() { }

    public Foo(string in1, string in2)
    {
        field1 = in1;
        field2 = in2;
    }

    public Foo(string in1, string in2, string in3, string in4)
    {
        field1 = in1;
        field2 = in2;
        field3 = in3;
    }

    public Prop1
    { get { return field1; } }
    { set { field1 = value; } }

    public Prop2
    { get { return field2; } }
    { set { field2 = value; } }

    public Prop3
    { get { return field3; } }
    { set { field3 = value; } }

    public Prop4
    { get { return field4; } }
    { set { field4 = value; } }

    public Prop5
    { get { return field5; } }
    { set { field5 = value; } }

}

Trong lớp "thực", chúng không phải là tất cả các chuỗi, nhưng trong một số trường hợp, chúng tôi có 30 trường sao lưu cho các thuộc tính hoàn toàn công khai.

Tôi ghét lớp học này, và tôi không biết nếu tôi chỉ kén chọn. Một vài điều cần lưu ý:

  • Các trường sao lưu riêng không có logic trong các thuộc tính, dường như không cần thiết và làm mờ lớp
  • Nhiều constructor (hơi ok) nhưng kết hợp với
  • tất cả các tài sản có một setter công khai, tôi không phải là một fan hâm mộ.
  • Có khả năng không có thuộc tính nào được gán một giá trị do hàm tạo trống, nếu người gọi không biết, bạn có thể nhận được một số rất không mong muốn và khó kiểm tra hành vi.
  • Đó là quá nhiều tài sản! (trong 30 trường hợp)

Tôi thấy khó khăn hơn nhiều để thực sự biết đối tượng Foođang ở trạng thái nào tại bất kỳ thời điểm nào, với tư cách là người thực hiện. Đối số được đưa ra "chúng ta có thể không có thông tin cần thiết để thiết lập Prop5tại thời điểm xây dựng đối tượng. Ok, tôi đoán tôi có thể hiểu điều đó, nhưng nếu đó là trường hợp chỉ Prop5đặt công khai, không phải lúc nào cũng có tối đa 30 thuộc tính trong một lớp.

Có phải tôi chỉ kén chọn và / hoặc điên vì muốn một lớp "dễ sử dụng" chứ không phải là "dễ viết (mọi thứ công khai)"? Các lớp học như tiếng hét ở trên với tôi, tôi không biết nó sẽ được sử dụng như thế nào, vì vậy tôi sẽ chỉ công khai mọi thứ trong trường hợp này .

Nếu tôi không quá kén chọn, những lý lẽ tốt để chống lại kiểu suy nghĩ này là gì? Tôi không giỏi trong việc tranh luận rõ ràng, vì tôi rất bực mình khi cố gắng đưa ra quan điểm của mình (tất nhiên là không cố ý).



3
Một đối tượng phải luôn ở trạng thái hợp lệ; đó là, bạn không cần phải có các đối tượng khởi tạo một phần . Một số thông tin không phải là một phần của trạng thái cốt lõi của đối tượng trong khi thông tin khác là (ví dụ: bảng phải có chân nhưng vải là tùy chọn); thông tin tùy chọn có thể được cung cấp sau khi khởi tạo, thông tin cốt lõi phải được cung cấp tại thời điểm khởi tạo. Nếu một số thông tin là cốt lõi nhưng không được biết ngay lúc khởi tạo thì tôi muốn nói rằng các lớp cần tái cấu trúc. Thật khó để nói nhiều hơn mà không biết bối cảnh của lớp học.
Marvin

1
Nói rằng "chúng tôi có thể không có thông tin cần thiết để đặt Prop5 tại thời điểm xây dựng đối tượng" khiến tôi phải hỏi "bạn có nói rằng sẽ có lúc bạn sẽ có thông tin đó vào lúc xây dựng và những lần khác khi bạn không có thông tin đó không? " Nó làm cho tôi tự hỏi nếu thực sự có hai điều khác nhau mà một lớp này đang cố gắng đại diện, hoặc có lẽ liệu lớp này có nên được chia thành các lớp nhỏ hơn không. Nhưng nếu nó được thực hiện "chỉ trong trường hợp" thì điều đó cũng tệ, IMO; điều đó nói với tôi rằng không có một thiết kế rõ ràng nào về cách các đối tượng được tạo / cư trú.
Học viên của Tiến sĩ Wily vào

3
Đối với các trường sao lưu riêng không có logic trong thuộc tính, có lý do nào khiến bạn không sử dụng thuộc tính tự động không?
Carson63000

2
@Kritner toàn bộ quan điểm của các thuộc tính tự động là nếu bạn cần logic thực sự ở đó trong tương lai, bạn có thể thêm nó một cách liền mạch vào vị trí tự động gethoặc set:-)
Carson63000 11/03/2016

Câu trả lời:


10

Các lớp hoàn toàn công khai có sự biện minh cho các tình huống nhất định, cũng như các lớp cực đoan khác, chỉ có một phương thức công khai (và có thể có rất nhiều phương thức riêng). Và các lớp học với một số công khai, một số phương pháp riêng tư là tốt.

Tất cả phụ thuộc vào kiểu trừu tượng mà bạn sẽ lập mô hình với chúng, bạn có lớp nào trong hệ thống của mình, mức độ đóng gói bạn cần trong các lớp khác nhau và (tất nhiên) trường phái nào mà tác giả của lớp học nghĩ đến từ. Bạn có thể tìm thấy tất cả các loại này trong mã RẮN.

Có toàn bộ sách viết về thời điểm thích loại thiết kế nào, vì vậy tôi sẽ không liệt kê bất kỳ quy tắc nào ở đây về nó, không gian trong phần này sẽ không đủ. Tuy nhiên, nếu bạn có một ví dụ thực tế cho một sự trừu tượng mà bạn muốn mô hình hóa với một lớp, tôi chắc chắn cộng đồng ở đây sẽ vui vẻ giúp bạn cải thiện thiết kế.

Để giải quyết các điểm khác của bạn:

  • "Các trường sao lưu riêng không có logic trong các thuộc tính": Vâng, bạn đã đúng, đối với các getters và setters tầm thường thì đây chỉ là "tiếng ồn" không cần thiết. Để tránh kiểu "phình to" này, C # có một cú pháp rút gọn cho các phương thức get / set thuộc tính:

Vì vậy, thay vì

   private string field1;
   public string Prop1
   { get { return field1; } }
   { set { field1 = value; } }

viết

   public string Prop1 { get;set;}

hoặc là

   public string Prop1 { get;private set;}
  • "Nhiều nhà xây dựng": bản thân nó không phải là vấn đề. Nó gặp vấn đề khi có sự sao chép mã không cần thiết trong đó, như được hiển thị trong ví dụ của bạn hoặc hệ thống phân cấp cuộc gọi bị phá vỡ. Điều này có thể được giải quyết dễ dàng bằng cách tái cấu trúc các phần chung thành một hàm riêng biệt và bằng cách tổ chức chuỗi hàm tạo theo cách đơn hướng

  • "Có khả năng không có thuộc tính nào được gán một giá trị do hàm tạo trống": trong C #, mọi kiểu dữ liệu có một giá trị mặc định được xác định rõ ràng. Nếu các thuộc tính không được khởi tạo rõ ràng trong một hàm tạo, chúng sẽ được gán giá trị mặc định này. Nếu điều này được sử dụng có chủ ý , nó hoàn toàn ổn - vì vậy một hàm tạo trống có thể ổn nếu tác giả biết mình đang làm gì.

  • "Đó là quá nhiều tài sản! (Trong 30 trường hợp)": có, nếu bạn có thể tự do thiết kế một lớp như vậy theo cách của trường xanh, 30 là quá nhiều, tôi đồng ý. Tuy nhiên, không phải ai trong chúng ta cũng có sự xa xỉ này (bạn đã không viết trong bình luận bên dưới nó có phải là một hệ thống kế thừa không?). Đôi khi bạn phải ánh xạ các bản ghi từ cơ sở dữ liệu hoặc tệp hoặc dữ liệu từ API của bên thứ ba vào hệ thống của bạn. Vì vậy, đối với những trường hợp này, 30 thuộc tính có thể là thứ mà người ta phải sống cùng.


Cảm ơn, vâng tôi biết POCO / POJO có vị trí của họ và ví dụ của tôi khá trừu tượng. Tôi không nghĩ rằng tôi có thể có được cụ thể hơn mà không cần đi vào một cuốn tiểu thuyết.
Kritner

@Kritner: xem bản chỉnh sửa của tôi
Doc Brown

vâng, thông thường khi tôi tạo một lớp hoàn toàn mới, tôi đi theo con đường sở hữu ô tô. Có các trường sao lưu riêng "chỉ vì" là (trong tâm trí của tôi) không cần thiết và thậm chí gây ra vấn đề. Khi bạn có ~ 30 thuộc tính cho một lớp và hơn 100 lớp, sẽ không quá xa để nói rằng sẽ có một số cài đặt Thuộc tính hoặc nhận được từ trường sao lưu không chính xác ... Thực tế tôi đã gặp phải điều này nhiều lần trong quá trình tái cấu trúc của chúng tôi :)
Kritner 10/03/2016

cảm ơn, giá trị mặc định cho chuỗi nulllà nó không? vì vậy nếu một trong những thuộc tính thực sự được sử dụng trong một .ToUpper(), nhưng giá trị không bao giờ được đặt cho nó, thì nó sẽ ném ngoại lệ thời gian chạy. Đây là một ví dụ điển hình tại sao cần dữ liệu cho lớp nên được thiết lập trong quá trình thi đối tượng. Không chỉ để nó cho người dùng. Cảm ơn
Kritner 11/03/2016

Ngoài quá nhiều thuộc tính chưa đặt, vấn đề chính với lớp này là nó có logic không (ZRP) và là nguyên mẫu cho Mô hình thiếu máu. Không cần điều này có thể diễn tả bằng lời, chẳng hạn như DTO, đó là một thiết kế kém.
dùng949300

2
  1. Bằng cách nói rằng "Các trường sao lưu riêng không có logic trong các thuộc tính, dường như không cần thiết và làm mờ lớp", bạn đã có một đối số hợp lý.

  2. Có vẻ như nhiều nhà xây dựng là vấn đề ở đây.

  3. Đối với việc có tất cả các thuộc tính là công khai ... Có thể nghĩ về nó theo cách này, nếu bạn từng muốn đồng bộ hóa quyền truy cập vào tất cả các thuộc tính này trong số các luồng bạn sẽ gặp khó khăn vì bạn có thể phải viết mã đồng bộ hóa ở mọi nơi đã sử dụng. Tuy nhiên, nếu tất cả các thuộc tính được bao quanh bởi getters / setters thì bạn có thể dễ dàng xây dựng đồng bộ hóa vào lớp.

  4. Tôi nghĩ khi bạn nói "khó kiểm tra hành vi" thì lập luận đã nói lên điều đó. Thử nghiệm của bạn có thể phải kiểm tra xem nó không phải là null hay đại loại như thế (mỗi nơi một tài sản được sử dụng). Tôi thực sự không biết vì tôi không biết bài kiểm tra / ứng dụng của bạn trông như thế nào.

  5. Bạn đúng cách quá nhiều thuộc tính, bạn có thể thử sử dụng tính kế thừa (nếu các thuộc tính đủ tương tự) và sau đó có một danh sách / mảng sử dụng tổng quát? Sau đó, bạn có thể viết getters / setters để truy cập thông tin và đơn giản hóa cách bạn sẽ tương tác với tất cả các thuộc tính.


1
Cảm ơn - # 1 và # 4 Tôi chắc chắn cảm thấy thoải mái nhất khi đưa ra lập luận của mình. Không hoàn toàn chắc chắn những gì bạn có ý nghĩa trong # 3. # 5 hầu hết các thuộc tính trong các lớp tạo nên khóa chính trên db. Chúng tôi sử dụng các khóa cạnh tranh, đôi khi lên tới 8 cột - nhưng đó là một vấn đề khác. Tôi đã suy nghĩ về việc cố gắng đưa các phần tạo nên khóa vào lớp / struct của riêng nó, điều này sẽ loại bỏ rất nhiều thuộc tính (ít nhất là trong lớp này ) - nhưng đây là một ứng dụng kế thừa với hàng ngàn dòng mã với nhiều người gọi Vì vậy, tôi đoán rằng tôi có rất nhiều điều để suy nghĩ :)
Kritner

Hở. Nếu lớp là bất biến, sẽ không có lý do để viết bất kỳ mã đồng bộ hóa nào trong lớp.
RubberDuck

@RubberDuck Lớp này có bất biến không? Ý anh là gì?
Snoop

3
Đó chắc chắn không phải là bất biến @StevieV. Bất cứ điều gì lớp với setters tài sản là có thể thay đổi.
RubberDuck

1
@Kritner Làm thế nào để các thuộc tính được sử dụng làm khóa chính? Điều đó có lẽ yêu cầu một số logic (kiểm tra null) hoặc quy tắc (ví dụ: NAME ưu tiên hơn AGE). Theo OOP, mã này thuộc về lớp này. Bạn có thể sử dụng nó như là một đối số?
dùng949300

2

Như bạn nói với tôi, Foolà một đối tượng kinh doanh. Nó nên gói gọn một số hành vi trong các phương thức theo tên miền của bạn và dữ liệu của nó phải được gói gọn nhất có thể.

Trong ví dụ bạn hiển thị, Footrông giống DTO hơn và đi ngược lại tất cả các nguyên tắc OOP (xem Mô hình miền thiếu máu )!

Khi cải tiến tôi sẽ đề xuất:

  • Làm cho lớp này là bất biến nhất có thể. Như bạn đã nói, bạn muốn thực hiện một số thử nghiệm đơn vị. Một khả năng bắt buộc để kiểm tra đơn vị là tính xác định và tính xác định có thể đạt được thông qua tính bất biến vì nó giải quyết được các vấn đề tác dụng phụ .
  • Chia lớp thần này thành nhiều lớp chỉ làm 1 việc (xem SRP ). Đơn vị kiểm tra một lớp thuộc tính 30 trong Pita thực sự .
  • Loại bỏ sự dư thừa của hàm tạo bằng cách chỉ có 1 hàm tạo chính và các hàm khác gọi hàm tạo chính.
  • Loại bỏ các getters không cần thiết, tôi nghiêm túc nghi ngờ tất cả các getters là thực sự hữu ích.
  • Mang lại logic kinh doanh trong các lớp kinh doanh, đây là nơi nó thuộc về!

Đây có thể là một lớp tái cấu trúc tiềm năng với những nhận xét sau:

public sealed class Foo
{
    private readonly string field1;
    private readonly string field2;
    private readonly string field3;
    private readonly string field4;

    public Foo() : this("default1", "default2")
    { }

    public Foo(string in1, string in2) : this(in1, in2, "default3", "default4")
    { }

    public Foo(string in1, string in2, string in3, string in4)
    {
        field1 = in1;
        field2 = in2;
        field3 = in3;
        field4 = in4;
    }

    //Methods with business logic

    //Really needed ?
    public Prop1
    { get; }

    //Really needed ?
    public Prop2
    { get; }

    //Really needed ?
    public Prop3
    { get; }

    //Really needed ?
    public Prop4
    { get; }

    //Really needed ?
    public Prop5
    { get; }
}

2

Làm thế nào để tranh luận chống lại điều này, tư duy hoàn toàn công khai về thiết kế lớp đối tượng kinh doanh

  • "Có một số phương thức nằm rải rác trong (các) lớp khách hàng thực hiện nhiều hơn 'nhiệm vụ DTO'. Chúng thuộc về nhau."
  • "Nó có thể là 'DTO' nhưng nó có bản sắc kinh doanh - chúng tôi cần ghi đè bằng."
  • "Chúng tôi cần sắp xếp những thứ này - chúng tôi cần triển khai IComparable"
  • "Chúng tôi đang kết hợp một số thuộc tính này. Hãy ghi đè ToString để mọi khách hàng không phải viết mã này."
  • "Chúng tôi có nhiều khách hàng đang làm điều tương tự với lớp này. Chúng tôi cần DRY lên mã."
  • "Khách hàng đang thao túng một bộ sưu tập những thứ này. Rõ ràng nó phải là một lớp bộ sưu tập tùy chỉnh; và nhiều điểm trước đó sẽ làm cho bộ sưu tập tùy chỉnh đó có nhiều chức năng hơn hiện tại."
  • "Hãy nhìn vào tất cả các thao tác chuỗi tẻ nhạt, lộ liễu! Đóng gói rằng trong các phương pháp liên quan đến kinh doanh sẽ tăng năng suất mã hóa của chúng tôi."
  • "Kéo các phương thức này lại với nhau trong lớp sẽ khiến chúng có thể kiểm tra được vì chúng ta không phải đối phó với lớp máy khách phức tạp hơn."
  • "Bây giờ chúng ta có thể đơn vị kiểm tra các phương thức được tái cấu trúc đó. Vì vậy, vì lý do x, y, z, máy khách không thể kiểm tra được.

  • Trong phạm vi, bất kỳ đối số nào ở trên có thể được đưa ra, tổng hợp:

    • Các chức năng trở nên có thể sử dụng lại.
    • Đó là DRY
    • Nó được tách ra từ các khách hàng.
    • mã hóa chống lại lớp là nhanh hơn và ít bị lỗi hơn.
  • "Một lớp được thực hiện tốt che giấu trạng thái và hiển thị chức năng. Đây là đề xuất bắt đầu để thiết kế các lớp OO."
  • "Kết quả cuối cùng thực hiện công việc tốt hơn nhiều trong việc thể hiện lĩnh vực kinh doanh; các thực thể riêng biệt và tương tác rõ ràng của chúng."
  • Chức năng "trên cao" này (nghĩa là không yêu cầu chức năng rõ ràng, nhưng nó phải được thực hiện) rõ ràng nổi bật và tuân thủ Nguyên tắc Trách nhiệm Đơn lẻ.
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.