2011-07-22 39 views
28

Như bạn có thể thấy trong đoạn mã bên dưới, phương thức DoStuff() được gọi trước Init() trong khi xây dựng một đối tượng Child.C# Làm thế nào để thực thi mã sau khi xây dựng đối tượng (postconstruction)

Tôi đang trong tình huống có nhiều lớp học cho trẻ em. Do đó, lặp lại một cuộc gọi đến phương thức DoStuff() ngay sau Init() trong hàm khởi tạo của mỗi đứa trẻ sẽ không phải là một giải pháp thanh lịch.

Có cách nào để tôi có thể tạo một số loại hàm tạo sau trong lớp cha sẽ được thực hiện sau hàm tạo của con không? Bằng cách này, tôi có thể gọi phương thức DoStuff() ở đó.

Nếu bạn có bất kỳ ý tưởng thiết kế nào khác có thể giải quyết được vấn đề của tôi, tôi cũng muốn nghe nó!

abstract class Parent 
{ 
    public Parent() 
    { 
     DoStuff(); 
    } 

    protected abstract void DoStuff(); 
} 

class Child : Parent 
{ 
    public Child() 
    // DoStuff is called here before Init 
    // because of the preconstruction 
    { 
     Init(); 
    } 

    private void Init() 
    { 
     // needs to be called before doing stuff 
    } 

    protected override void DoStuff() 
    { 
     // stuff 
    } 
} 
+0

Tìm thấy liên kết này hiển thị thứ tự xây dựng ... http://www.csharp411.com/c-object-initialization/ về cơ bản cho biết rằng hàm tạo cá thể có nguồn gốc của bạn là cuối cùng trong danh sách công cụ cần làm. Tôi nghĩ rằng kéo DoStuff ra khỏi constructor lớp cơ sở và rõ ràng gọi nó là cách tiếp cận trực tiếp nhất, hoặc cung cấp một lớp cơ sở Init có thể được ghi đè. – deepee1

+0

+1: Tôi không biết tại sao nhưng tôi luôn (sai) cho rằng hàm tạo con được gọi là đầu tiên; đó là lý do tại sao tôi luôn luôn sử dụng: base() trong các hàm tạo của tôi. Cảm ơn vì đã giáo dục tôi. – NotMe

Trả lời

7

Làm thế nào về điều này:

abstract class Parent 
{ 
    public Parent() 
    { 
     Init(); 
     DoStuff(); 
    } 

    protected abstract void DoStuff(); 
    protected abstract void Init(); 
} 

class Child : Parent 
{ 
    public Child() 
    { 
    } 

    protected override void Init() 
    { 
     // needs to be called before doing stuff 
    } 

    protected override void DoStuff() 
    { 
     // stuff 
    } 
} 
+0

Tôi muốn tranh luận gọi Init() 'trước khi' DoStuff' của cha mẹ trong 'DoStuff' của con là lựa chọn tốt hơn, để lớp cơ sở vẫn không biết về sự cần thiết phải thực hiện các hoạt động khởi tạo (trừ khi tất nhiên, tất cả các lớp con sẽ kết thúc đòi hỏi nó ...) –

+0

Nhưng DoStuff của phụ huynh là trừu tượng, không thực sự là một phương pháp ở đó. Cả DoStuff và Init đều có mặt trên Child. Tôi đồng ý mặc dù, nếu một số trẻ em không cần phải gọi Init thì giải pháp này trở nên rắc rối. – taylonr

+2

-1 Bạn không thấy rằng phân cấp lớp và khởi tạo hàm tạo bao gồm cả DoStuff() và Init() trong thực hiện hiện tại không? Đó là những phần vô dụng của mã số junky trong trường hợp này là –

14

Nếu bạn có một logic phức tạp để xây dựng đối tượng của bạn sau đó xem xét FactoryMethod mẫu.

Trong trường hợp của bạn tôi sẽ thực hiện nó như là một phương pháp đơn giản

public static Parent Construct(someParam) 

mà sẽ đưa một số thông số và dựa trên đó quyết định những đứa trẻ lớp để nhanh chóng. Bạn có thể xóa cuộc gọi phương thức DoStuff() của mình khỏi hàm tạo và gọi nó trong số Construct() trên phiên bản mới.

Ngoài ra, bạn nên tránh các cuộc gọi phương thức ảo/trừu tượng trong các hàm tạo. Xem câu hỏi này để biết thêm chi tiết: Virtual member call in a constructor

1

Correction: Theo this answer, bạn không thể xác định khi constructor lớp cơ sở được gọi trong quá trình thi của lớp con.

Ví dụ: Điều này không làm việc:

Vì vậy, có, như những người khác đã lưu ý, nếu chuỗi các sự kiện quan trọng, sau đó các lớp cơ sở cần phải có khả năng thích ứng bằng cách tuyên bố rằng phương pháp trừu tượng theo thứ tự, hoặc (tốt hơn chưa) bằng cách triển khai DoStuff của lớp con đại diện cho chuỗi sự kiện:

protected override void DoStuff() 
{ 
    Init(); 
    base.DoStuff(); 
} 
+0

Trong * C# *? Tôi biết bạn có thể làm những việc như thế này trong C++ –

+0

-1 - base() trong phần thân của hàm tạo sẽ không biên dịch –

+0

Được thừa nhận và sửa chữa, thay thế. –

1

DoStuff là trừu tượng. Chỉ cần gọi Init từ đầu DoStuff.

+0

Đối với kịch bản của tôi, Init() sẽ cần phải được gọi trong DoStuff() ghi đè của mỗi đứa trẻ, mà không phải là tốt hơn so với đặt DoStuff() trong constructor của mỗi đứa trẻ. Ngoài ra, Init() có thể có thể cần thiết cho các phương pháp khác, do đó nó cần phải được thực hiện một nơi nào đó trong xây dựng. – asmo

+1

Điều đó làm cho giải pháp của taylonr khả thi hơn nhiều. –

+0

+1 Của anh ta và của bạn đều tốt. – hoodaticus

0

Thay vì sử dụng một phương pháp abstract, mà sẽ yêu cầu bạn phải thực hiện các phương pháp trong mọi tầng lớp hậu duệ, bạn có thể thử:

public class Parent 
{ 
    public Parent() 
    { 
     PostConstructor(); 
    } 


    protected virtual void PostConstructor() 
    { 
    } 
} 

public class Child : Parent 
{ 
    protected override void PostConstructor() 
    { 
     base.PostConstructor(); 

     /// do whatever initialization here that you require 
    } 
} 

public class ChildWithoutOverride 
{ 
    /// not necessary to override PostConstructor 
} 
+0

K.I.S.S. bạn;) –

+0

@Artur? Điều đó khá đơn giản. –

+0

Có 1 cho sự sang trọng, tôi là một lời xin lỗi của K.I.S.S. nếu không thì những khuôn khổ nhỏ bé có thể dễ dàng trở thành quái dị. –

4

Tôi mạnh mẽ sẽ đề nghị sử dụng Nhà máy như mẫu.

Nếu nó có thể:

. Đẩy tất cả các lớp con và lớp trừu tượng của bạn vào cụm riêng biệt.

. Khai báo các ctors của childs như các method nội bộ, vì vậy không ai trong assembly đó có thể xây dựng chúng chỉ bằng cách gọi ctor.

. Triển khai lớp Factory để xây dựng cho kiểu đối tượng được chỉ định của người gọi, obviuoly sẽ forse gọi phương thức DoStuff() trừu tượng sau khi tạo ra anobject, nhưng trước khi trả về cho người gọi.

Điều tốt về điều này là: Nó sẽ cung cấp cho bạn mức trừu tượng bổ sung, vì vậy trong tương lai bạn sẽ cần thêm một số chức năng gọi hoặc bất kỳ loại phức tạp logic nào khác, những gì bạn cần thêm chúng vào lớp Nhà máy của bạn.

Tức là.

Trân

+0

Ý tưởng cho khi lắp ráp riêng biệt là không thể: cho DEBUG xây dựng, có một phương pháp trong ctor cơ sở nhìn vào ngăn xếp và chắc chắn rằng nó đã được gọi bằng phương pháp nhà máy. –

+0

Một ý tưởng thay thế khác: di chuyển các lớp con thành không gian tên riêng của nó, có lẽ với một tên như MyNamespace.DoNotUseThisUseFactoryMethodInstead, vì vậy bạn không sử dụng ...; nó và sử dụng ctor con một cách tình cờ. –

0

Trong các ứng dụng WPF, bạn có thể trì hoãn việc invokation của DoStuff() với sự giúp đỡ của Dispatcher:

abstract class Parent 
{ 
    public Parent() 
    { 
     Dispatcher.CurrentDispatcher.BeginInvoke(new Action(this.DoStuff)); 
    } 

    private void DoStuff() 
    { 
     // stuff, could also be abstract or virtual 
    } 
} 

Tuy nhiên, nó không được bảo đảm rằng DoStuff() sẽ được gọi ngay sau khi các nhà xây dựng.

2

Như những người khác đã đề cập, bạn nên sử dụng Mẫu nhà máy.

public class Parent 
{ 
    public Parent() 
    {    
    } 

    public virtual void PostConstructor() 
    { 
    } 
} 

public class Child : Parent 
{ 
    public override void PostConstructor() 
    { 
     base.PostConstructor(); 

     // Your code here 
    } 
} 

public void FactoryMethod<T>() where T : Parent 
{ 
    T newobject = new T(); 
    newobject.PostConstructor(); 
} 
0

Hãy để tôi giới thiệu một giải pháp chung sử dụng một số tính năng C#. Lưu ý rằng giải pháp này không yêu cầu bạn sử dụng mẫu nhà máy hoặc gọi bất kỳ thứ gì sau khi xây dựng đối tượng và nó hoạt động trên bất kỳ lớp nào chỉ bằng cách triển khai thực hiện một giao diện với một phương thức duy nhất. Đầu tiên chúng ta khai báo một giao diện mà lớp học của chúng tôi sẽ phải thực hiện:

public interface IInitialize { 
    void OnInitialize(); 
} 

Tiếp theo chúng ta thêm một lớp mở rộng tĩnh cho giao diện này, và thêm các phương pháp Initialize:

public static class InitializeExtensions 
{ 
    public static void Initialize<T>(this T obj) where T: IInitialize 
    { 
     if (obj.GetType() == typeof(T))  
      obj.OnInitialize(); 
    } 
} 

Bây giờ, nếu chúng ta cần một lớp và tất cả các hậu duệ của nó gọi một initializer ngay sau khi đối tượng được xây dựng hoàn chỉnh, tất cả những gì chúng ta cần làm là thực hiện IInitialize và nối thêm một dòng trong hàm tạo:

public class Parent : IInitialize 
{ 
    public virtual void OnInitialize() 
    { 
     Console.WriteLine("Parent"); 
    } 

    public Parent() 
    { 
     this.Initialize(); 
    } 
} 

public class Child : Parent 
{ 
    public Child() 
    { 
     this.Initialize(); 
    } 

    public override void OnInitialize() 
    { 
     Console.WriteLine("Child"); 
    } 
} 

public class GrandChild : Child 
{ 
    public GrandChild() 
    { 
     this.Initialize(); 
    } 

    public override void OnInitialize() 
    { 
     Console.WriteLine("GrandChild"); 
    } 
} 

Bí quyết là khi một lớp dẫn xuất gọi phương thức mở rộng Initialize, điều này sẽ ngăn chặn mọi cuộc gọi không được thực hiện từ lớp thực tế.

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