2009-06-03 27 views
9

Xét đoạn code mẫu sau:Ưu và nhược điểm của thuộc tính 'mới' trong C#/.Net?

// delivery strategies 
public abstract class DeliveryStrategy { ... } 
public class ParcelDelivery : DeliveryStrategy { ... } 
public class ShippingContainer : DeliveryStrategy { ... } 

và lớp mẫu theo thứ tự sau đây:

// order (base) class 
public abstract class Order 
{ 
    private DeliveryStrategy delivery; 

    protected Order(DeliveryStrategy delivery) 
    { 
     this.delivery = delivery; 
    } 

    public DeliveryStrategy Delivery 
    { 
     get { return delivery; } 
     protected set { delivery = value; } 
    } 
} 

Khi tôi lấy được một loại mới của lớp trật tự, nó sẽ thừa hưởng tài sản Cung cấp các loại DeliveryStrategy.

Bây giờ, khi nó được đưa ra rằng CustomerOrders phải được giao sử dụng chiến lược ParcelDelivery, chúng tôi có thể xem xét 'mới' ing thuộc tính giao hàng trong lớp CustomerOrder:

public class CustomerOrder : Order 
{ 
    public CustomerOrder() 
     : base(new ParcelDelivery()) 
    { } 

    // 'new' Delivery property 
    public new ParcelDelivery Delivery 
    { 
     get { return base.Delivery as ParcelDelivery; } 
     set { base.Delivery = value; } 
    } 
} 

(Các CustomerOrder rõ ràng là cần phải đảm bảo rằng tương thích (đa hình) với Đặt hàng)

Điều này cho phép sử dụng trực tiếp chiến lược Chuyển phát bưu kiện trên CustomerOrder mà không cần phải truyền.

Bạn có cân nhắc sử dụng mẫu này không? tại sao tại sao không?

Cập nhật: tôi đã đưa ra mẫu này, thay vì sử dụng Generics, vì tôi muốn sử dụng tính năng này cho nhiều thuộc tính. Tôi không muốn sử dụng các đối số kiểu chung cho tất cả các thuộc tính này

+2

Miễn là phương pháp che bóng của bạn không có điều kiện tiên quyết chặt chẽ hơn và không có điều kiện dự phòng yếu hơn, tôi nghĩ thực hành này là hoàn toàn có thể chấp nhận được. –

+0

Trong trường hợp đầu tiên, tại sao bạn cần phải cast DeliveryStrategy? Điểm của việc sử dụng mô hình chiến lược là các hành vi thực hiện một giao diện, và do đó có tất cả các phương thức giống nhau, do đó, không cần phải được đúc. –

Trả lời

6

Tôi nghĩ đây là một mô hình tốt.Nó làm cho nó dễ dàng hơn để sử dụng một cách rõ ràng các loại có nguồn gốc bằng cách loại bỏ sự cần thiết phải đúc kết quả, và nó không 'phá vỡ' hành vi lớp cơ sở. Trên thực tế, một mẫu tương tự được sử dụng trong một số lớp trong BCL, ví dụ nhìn vào hệ thống phân cấp lớp DbConnection:

  • DbConnection.CreateCommand() trả về một DbCommand
  • SqlConnection.CreateCommand() ẩn việc thực hiện cơ sở sử dụng 'mới' để trả về SqlCommand.
  • (triển khai DbConnection khác làm tương tự)

Vì vậy, nếu bạn thao tác các đối tượng kết nối thông qua một biến DbConnection, CreateCommand sẽ trả về một DbCommand; nếu bạn thao tác nó thông qua một biến SqlConnection, CreateCommand sẽ trả về một SqlCommand, tránh việc cast nếu bạn gán nó cho một biến SqlCommand.

+0

Được chấp nhận làm câu trả lời, để hiển thị các trường hợp mô phỏng từ khung .Net (mặc dù chúng không sử dụng 'mới' trong SqlConnection!) –

+1

"mặc dù chúng không sử dụng 'mới' trong SqlConnection": thực ra tôi gần như chắc chắn chúng do: đó là cách duy nhất để xác định một phương thức có cùng tên và tham số như trong lớp cơ sở, nhưng với một kiểu trả về khác. Tôi đoán bạn đã kiểm tra với Reflector, nhưng mã được hiển thị bởi Reflector không phải là mã nguồn thực tế: nó chỉ có thể hiển thị những gì đã được tạo ra trong IL, và dường như trình sửa đổi 'mới' không phát ra bất kỳ IL (hoặc ít nhất là Reflector doesn không hiển thị nó). –

+0

việc sử dụng mới trong mã nguồn đơn giản ngăn cản trình biên dịch cảnh báo bạn rằng nó đang thực hiện nó cho bạn anyway (có thể thay đổi tất nhiên) – ShuggyCoUk

5

Bạn có thể sử dụng Generics.

// order (base) class 
public abstract class Order<TDeliveryStrategy> where TDeliveryStrategy : DeliveryStrategy 
{ 
    private TDeliveryStrategy delivery; 

    protected Order(TDeliveryStrategy delivery) 
    { 
     this.delivery = delivery; 
    } 

    public TDeliveryStrategy Delivery 
    { 
     get { return delivery; } 
     protected set { delivery = value; } 
    } 
} 

public class CustomerOrder : Order<ParcelDelivery> 
{ 
    public CustomerOrder() 
     : base(new ParcelDelivery()) 
    { } 
} 
11

tôi muốn làm kiểu generic:

public abstract class Order<TDelivery> where TDelivery : Delivery 
{ 
    public TDelivery Delivery { ... } 
    ... 
} 

public class CustomerOrder : Order<ParcelDelivery> 
{ 
    ... 
} 

Điều này đảm bảo an toàn loại tại thời gian biên dịch, chứ không phải rời khỏi nó lên đến thời gian thực hiện. Nó cũng ngăn chặn tình huống:

CustomerOrder customerOrder = new CustomerOrder(); 
Order order = customerOrder; 
order.Delivery = new NonParcelDelivery(); // Succeeds! 

ParcelDelivery delivery = customerOrder.Delivery; // Returns null 

Ouch.

Tôi coi new như thường là phương sách cuối cùng. Nó giới thiệu thêm sự phức tạp cả về triển khai và sử dụng.

Nếu bạn không muốn đi xuống tuyến đường chung, tôi sẽ giới thiệu một tài sản thực sự mới (với một tên khác).

+0

Tôi không thể chờ C# 4 với contravariance trong các loại trả về. – erikkallen

+0

Loại trả về biến đổi, kiểu tham số contravariant :) Tuy nhiên, đó chỉ dành cho giao diện và chỉ trong một số trường hợp nhất định như tôi biết ... –

+0

@Jon: tôi đã thiết lập thuộc tính bảo vệ cho trường hợp chính xác này. Lớp cơ sở và lớp dẫn xuất cần phải làm việc cùng nhau để làm việc này. –

1

Có lý do nào khiến bạn cần thay đổi loại trả lại không? Nếu không có thì tôi sẽ đề nghị chỉ làm cho tài sản giao hàng ảo nên nó phải được xác định bởi các lớp kế thừa thay vì:

public abstract class Order 
{ 
    protected Order(DeliveryStrategy delivery) 
    { 
     Delivery = delivery; 
    } 

    public virtual DeliveryStrategy Delivery { get; protected set; } 
} 

public class CustomerOrder : Order 
{ 
    public CustomerOrder() 
     : base(new ParcelDelivery()) 
    { } 

    public DeliveryStrategy Delivery { get; protected set; } 
} 

Nếu bạn làm đòi hỏi sự thay đổi trong kiểu trả về, sau đó tôi sẽ tự hỏi tại sao bạn sẽ cần sự quyết liệt của hành vi thay đổi trên kiểu trả về. Bất kể, nếu đó là như vậy, sau đó điều này sẽ không làm việc cho bạn. Vì vậy, để trực tiếp trả lời câu hỏi của bạn, tôi sẽ chỉ sử dụng mẫu mà bạn đã mô tả nếu yêu cầu kiểu trả về khác với lớp cơ sở và rất ít (tôi sẽ phân tích mô hình đối tượng của mình để xem liệu đó có phải không?). cái gì khác tôi có thể làm đầu tiên). Nếu đó không phải là trường hợp, thì tôi sẽ sử dụng mẫu mà tôi đã mô tả ở trên.

3

Sử dụng từ khóa 'mới' để ẩn thuộc tính có thể ghi từ lớp cơ sở là ý tưởng tồi trong quan điểm của tôi. Từ khóa mới cho phép bạn ẩn một thành viên của một lớp cơ sở trong một lớp dẫn xuất, thay vì ghi đè lên nó. Điều này có nghĩa rằng các cuộc gọi đến các thành viên như vậy bằng cách sử dụng tham chiếu lớp cơ sở vẫn truy cập mã lớp cơ sở, không phải mã lớp dẫn xuất. C# có từ khóa 'ảo', cho phép các lớp dẫn xuất thực sự thực hiện override, thay vì chỉ đơn giản là ẩn nó. Có một bài báo khá hợp lý here nói về sự khác biệt.

Trong trường hợp của bạn, có vẻ như bạn đang cố gắng sử dụng phương thức ẩn như cách giới thiệu property covariance tới C#. Tuy nhiên, có những vấn đề với cách tiếp cận này.

Thông thường, giá trị của việc có lớp cơ sở là cho phép người dùng mã của bạn xử lý các loại đa hình. Điều gì xảy ra với việc triển khai của bạn nếu ai đó đặt thuộc tính Phân phối bằng cách sử dụng tham chiếu đến lớp cơ sở? Lớp dẫn xuất có bị phá vỡ nếu thuộc tính Delivery không phải là một thể hiện của ParcelDelivery không? Đây là các loại câu hỏi bạn cần để tự hỏi về lựa chọn triển khai này.

Bây giờ, nếu thuộc tính Phân phối trong lớp cơ sở không cung cấp bộ đặt, thì bạn có tình huống hơi khác. Người dùng của lớp cơ sở chỉ có thể truy xuất thuộc tính và không đặt nó. Vì bạn định tuyến truy cập thuộc tính của bạn trở lại lớp cơ sở, truy cập thông qua lớp cơ sở tiếp tục hoạt động. Tuy nhiên, nếu lớp dẫn xuất của bạn không được niêm phong, các lớp thừa kế từ nó có thể giới thiệu cùng một loại vấn đề bằng cách ẩn thuộc tính Phân phối bằng phiên bản của riêng chúng.

Như một số bài viết khác đã đề cập, bạn có thể sử dụng Generics như một cách để đạt được các loại thuộc tính Phân phối khác nhau. Ví dụ của Jon khá giỏi trong việc chứng minh điều này. Có một vấn đề với cách tiếp cận của generics, nếu bạn cần lấy được từ CustomerOrder và thay đổi thuộc tính Delivery thành một kiểu mới.

Có một giải pháp thay thế cho Generics. Bạn cần phải xem xét liệu bạn có thực sự muốn một tài sản có thể định cư trong trường hợp của bạn hay không. Nếu bạn thoát khỏi setter trên thuộc tính Delivery, các vấn đề được giới thiệu bằng cách sử dụng lớp Order trực tiếp biến mất. Vì bạn đặt thuộc tính phân phối bằng cách sử dụng các tham số hàm tạo, bạn có thể đảm bảo rằng tất cả các đơn đặt hàng đều có đúng loại chiến lược.

1

Hãy xem xét cách tiếp cận này:

public interface IOrder 
{ 
    public DeliveryStrategy Delivery 
    { 
     get; 
    } 
} 

// order (base) class 
public abstract class Order : IOrder 
{ 
    protected readonly DeliveryStrategy delivery; 

    protected Order(DeliveryStrategy delivery) 
    { 
     this.delivery = delivery; 
    } 

    public DeliveryStrategy Delivery 
    { 
     get { return delivery; } 
    } 
} 

sau đó sử dụng

public class CustomerOrder : Order 
{ 
    public CustomerOrder() 
     : base(new ParcelDelivery()) 
    { } 

    public ParcelDelivery Delivery 
    { 
     get { return (ParcelDelivery)base.Delivery; } 
    } 


    DeliveryStrategy IOrder.Delivery 
    { 
     get { return base.Delivery} 
    } 
} 

này vẫn còn xa mới hoàn hảo (ví dụ bạn không hiển thị tại sao lớp cơ sở cần biết về chiến lược giao ở tất cả và nó sẽ làm cho nhiều ý nghĩa hơn để được chung chung với một hạn chế nhưng điều này ít nhất cho phép bạn sử dụng cùng một tên cho tài sản và nhận được an toàn loại.

Như trong ví dụ của bạn là vô nghĩa, nếu một cái gì đó là không đúng loại bạn không nên che giấu nó bằng null bạn nên ném như bất biến của bạn đã bị vi phạm.

Trường chỉ đọc luôn thích hợp hơn nếu có thể. Họ làm cho sự bất biến rõ ràng.

+0

Tôi thích sự thay thế mà giao diện cung cấp, nhưng tôi tin rằng mẫu này làm cho mô hình phức tạp hơn (có hai thuộc tính Phân phối trên cùng một lớp). –

+0

Trong trường hợp không có phương thức co/contra, bạn phải tự mình thực hiện hoặc kết thúc tiêu dùng (tên/phôi khác nhau) hoặc tại nhà cung cấp (hai thuộc tính tương tự như tôi hoặc theo bóng) Trong tất cả các tình huống của nhà cung cấp, sẽ có hai tài sản (bạn chỉ không nhìn thấy nó với 'mới') – ShuggyCoUk

1

Giải pháp của bạn không làm những gì bạn nghĩ. Dường như nó hoạt động nhưng nó không gọi phương thức "mới" của bạn. Hãy xem xét các thay đổi sau mã của bạn để thêm một số đầu ra để xem phương thức được gọi là:

// order (base) class 
public abstract class Order 
{ 
    private DeliveryStrategy delivery; 

    protected Order(DeliveryStrategy delivery) 
    { 
     this.delivery = delivery; 
    } 

    public DeliveryStrategy Delivery 
    { 
     get 
     { 
      Console.WriteLine("Order"); 
      return delivery; 
     } 
     protected set { delivery = value; } 
    } 
} 

public class CustomerOrder : Order 
{ 
    public CustomerOrder() 
     : base(new ParcelDelivery()) 
    { } 

    // 'new' Delivery property 
    public new ParcelDelivery Delivery 
    { 
     get 
     { 
      Console.WriteLine("CustomOrder"); 
      return base.Delivery as ParcelDelivery; 
     } 
     set { base.Delivery = value; } 
    } 
} 

Sau đó, đoạn mã sau đây để thực sự sử dụng lớp CustomOrder của bạn:

Order o = new CustomerOrder(); 
var d = o.Delivery; 

Would sản lượng "Order ". Phương pháp mới đặc biệt phá vỡ đa hình. Nó tạo ra một thuộc tính Delivery mới trên CustomOrder mà không phải là một phần của lớp cơ sở Order. Vì vậy, khi bạn sử dụng CustomOrder như thể đó là một Lệnh, bạn không gọi thuộc tính Phân phối mới vì nó chỉ tồn tại trên CustomOrder và không phải là một phần của lớp Order.

Điều bạn đang cố gắng thực hiện là ghi đè phương thức không thể ghi đè. Nếu bạn có nghĩa là tài sản được overridable, làm cho nó trừu tượng:

// order (base) class 
public abstract class Order 
{ 
    private DeliveryStrategy delivery; 

    protected Order(DeliveryStrategy delivery) 
    { 
     this.delivery = delivery; 
    } 

    public abstract DeliveryStrategy Delivery 
    { 
     get { return delivery; } 
     protected set { delivery = value; } 
    } 
} 

public class CustomerOrder : Order 
{ 
    public CustomerOrder() 
     : base(new ParcelDelivery()) 
    { } 

    public override ParcelDelivery Delivery 
    { 
     get { return base.Delivery as ParcelDelivery; } 
     set { base.Delivery = value; } 
    } 
} 
+0

Nó wat tôi mong đợi nó để làm. Thay đổi Console.WriteLine ("Order") thành Console.WriteLine (delivery.GetType(). GetName()) (hoặc một số như vậy) và bạn sẽ thấy ;-) –

+0

delivery.GetType() sẽ cung cấp cho bạn ParcelDelivery vì bạn chuyển nó vào hàm tạo của Order, không phải vì bạn đang gọi thuộc tính Delivery mới của lớp con. Nếu bạn muốn sử dụng sự phản chiếu để xem bạn đang ở đâu, bạn đang thực sự gọi phương thức nào, hãy thay đổi văn bản thành: Console.WriteLine (System.Reflection.MethodBase.GetCurrentMethod(). DeclaringType.FullName); – ZombieDev

0

Sử dụng new để shadow thành viên ảo của một lớp cơ sở là một ý tưởng tồi, bởi vì loại phụ có nguồn gốc từ sẽ không thể ghi đè lên chúng đúng cách. Nếu có tồn tại các thành viên lớp mà người ta muốn ẩn trong các lớp dẫn xuất, các thành viên lớp cơ sở không được khai báo là abstract hoặc virtual, nhưng thay vào đó chỉ cần gọi một thành viên protected abstract hoặc protected virtual. Một kiểu dẫn xuất có thể đổ bóng phương thức lớp cơ sở với phương thức gọi thành viên protected thích hợp và kết quả phù hợp.

Các vấn đề liên quan