2009-12-10 30 views
10

vấn đề ông bà của là thế này: Tôi có một lớp trừu tượng mà hiện một số công việc trong constructor của nó, và một tập các lớp con mà thực hiện các lớp trừu tượng:Nhảy qua constructor mẹ gọi

class AbstractClass { 
    AbstractClass(){ /* useful implementation */ } 
} 

class ConcreteClass1 extends AbstractClass { 
    ConcreteClass1(){ super(); /* useful implementation */ } 
} 

Sau đó, lớp bê tông cần phải được tùy chỉnh và một giải pháp là để mở rộng các lớp bê tông:

class CustomizedClass1 extends ConcreteClass1 { 
    CustomizedCLass1(){ super(); /* useful implementation */ } 
} 

NHƯNG vấn đề là các lớp tùy chỉnh chỉ cần gọi constructor lớp trừu tượng và không constructor lớp bê tông của.

Làm thế nào để bạn đạt được điều này? Gợi ý thay đổi mối quan hệ của lớp là hợp lệ.

EDIT: Ví dụ cụ thể là ConcreteClass1 và CustomizedClass1 có các bộ dữ liệu khác nhau (ConcreteData1 và CustomizedData1) và được lấy từ cơ sở dữ liệu trong hàm tạo của lớp. Vấn đề là việc tạo một cá thể của CustomizedClass1 sẽ lấy ra cả hai thực thể dữ liệu.

Tôi biết rằng việc sử dụng thừa kế đơn giản có lẽ không phải là điều tốt nhất để làm, đó là lý do tại sao tôi chỉ ra rằng các đề xuất thay đổi mối quan hệ của lớp là hợp lệ.

+7

Nếu bạn không gọi hàm tạo 'ConcreteClass1', nó không phải là' ConcreteClass1'. –

+0

Một số câu trả lời cho thấy một số thủ thuật để làm điều này nhưng điều này rất không tự nhiên trong Java và OO. Các câu trả lời hay nhất sẽ thách thức thiết kế đối tượng của bạn và khuyến khích bạn suy nghĩ về các mối quan hệ A-A và A có. –

+0

Nhưng tốc độ phát triển đôi khi có thể có vấn đề, phải không? – mlvljr

Trả lời

9

dễ dàng (nhưng tại sao?):

class AbstractClass { 
    AbstractClass(){ /* useful implementation */ } 
} 

class ConcreteClass1 extends AbstractClass { 
    ConcreteClass1(){ super(); /* useful implementation */ } 
    ConcreteClass1(boolean skip){ super(); } 
} 
class CustomizedClass1 extends ConcreteClass1 { 
    CustomizedCLass1(){ super(true); /* useful implementation */ } 
} 
+0

không bao giờ nghĩ về điều đó ... nhưng tôi cũng hỏi "nhưng tại sao?" câu hỏi quá :-) – TofuBeer

+0

Đoán từ thiện của tôi là trong ConcreteClass1 ông có thể làm một số khởi tạo các nguồn lực bên ngoài, và trong CustomizedClass1 ông muốn tài nguyên bên ngoài khác nhau khởi tạo. – MattMcKnight

+1

nếu đây là trường hợp sau đó ông có thể cần phải tạo ra một lớp học mà là anh chị em của cha mẹ, hoặc refactor ConcreteClass1 thành hai lớp khác nhau. –

21

Bạn không thể làm điều này trong Java. Tôi thường xuyên có những sinh viên muốn làm điều này, nhưng tôi chưa bao giờ thấy một trường hợp mà nó thực sự là những gì họ muốn làm.

Bạn có thể cho một ví dụ cụ thể về những gì nó là bạn muốn làm và tại sao (mô tả của bạn là quá mơ hồ) và tôi chắc chắn một giải pháp có thể đạt được :-)

Edit:

Đối một ví dụ thế giới thực của lý do tại sao bạn không muốn làm điều này (thông thường) sẽ là một hệ thống phân cấp như:

Animal (constructor makes the eyes) 
    | 
Mammal (constructor makes the lungs) 
    | 
Human (constructor sets the language) 

Nếu các nhà xây dựng con người có thể bỏ qua các nhà xây dựng động vật có vú sau đó bạn sẽ đối mặt với vấn Human người không có phổi ... không hữu ích lắm.

+11

Động vật <-Mammal <-Aquaman: Kế thừa mọi thứ nhưng xây dựng phổi ở động vật có vú và thực hiện mang của riêng mình.;) – erickson

+1

Sau đó, các nhà xây dựng từ động vật có vú nên có một "cơ chế thở" như một đối số cho các nhà xây dựng và gán cho các biến thể hiện được sử dụng để được phổi :-) – TofuBeer

+1

Tôi nghĩ rằng điểm này là mất tích là Mammal constructor là không nhất thiết là idempotent và bạn không luôn muốn gọi cùng một hàm tạo từ một lớp con. Bạn vẫn phải gọi ít nhất một hàm tạo, nhưng không phải ai cũng cần cùng một hàm tạo. – MattMcKnight

8

Bất kỳ thể hiện của CustomizedClass1 cũng là một thể hiện của ConcreteClass1, theo định nghĩa, vì vậy nó phải được xây dựng như một ConcreteClass1 dụ hợp lệ trước khi các nhà xây dựng CustomizedClass1 có thể chạy. Nếu không, điều gì sẽ xảy ra nếu bạn gọi là ConcreteClass1 các phương pháp trên đó? Họ sẽ cố gắng hoạt động trên các biến chưa được khởi tạo.

Nếu bạn nghĩ rằng bạn cần phải làm điều này, rất có thể thiết kế của bạn cần suy nghĩ lại. Ví dụ: nếu bạn chỉ muốn một số chức năng từ ConcreteClass1, chức năng đó có thể được tính vào một lớp bậc trên của ConcreteClass1CustomizedClass1 có thể mở rộng để chỉ nhận được chức năng cần thiết.

Vui lòng cung cấp thêm thông tin về mối quan hệ giữa các lớp này.

1

Điều này nghe với tôi giống như một hỗn hợp của mối quan tâm - một cái gì đó Java không được trang bị tốt để xử lý.

Mặc dù đó không phải là câu trả lời bạn đang hy vọng hoặc tôi tự hào nhập, bạn chỉ cần tạo ConcreteClass2 bắt chước ConcreteClass1 và sử dụng hàm tạo của AbstractClass.

Như @TofuBeer đã nói, đây không phải là điều mà Java hỗ trợ. Đây là lý do tại sao một số ngôn ngữ hiện đại (ví dụ: Scala w/Traits) đang thu hút các nhà phát triển đam mê.

0

Tại sao không chỉ thực sự tùy chỉnh trường hợp ConcreteClass1 mới được tạo ra để hoạt động như một cá thể AbstractClass (với điều kiện là ConcreteClass1 có các phương thức được bảo vệ tương ứng cho điều đó)? Ví dụ:

class AbstractClass { 
    public AbstractClass() { /* useful implementation */ } 
} 

class ConcreteClass1 extends AbstractClass { 
    public ConcreteClass1() { 
     /* A hidden super() call put here by the compiler. */ 
     /* Useful implementation */ 
    } 

    protected void customize_with(SomeParams customization_params) { 
     /* 
     Customize this ConcreteClass1 instance according to parameters 
     passed. As a result, the instance behavior will 'revert' (in the 
     way you need it) to that of an AbstractClass instance. 
     */ 
    } 
} 

class CustomizedClass1 extends ConcreteClass1 { 
    public CustomizedCLass1() { 
     /* A hidden super() call put here by the compiler. */ 
     customize_with(customization_params); 
     /* Rest of useful implementation */ 
    } 
} 

Các ý định thiết kế và logic ở đây có thể thực hiện như sau:

  1. Bạn muốn nhận được (cơ bản) hành vi của ConcreteClass1 qua thừa kế, bạn kế thừa từ nó (này áp đặt nhiên thiết kế nó đáng kế thừa từ).

  2. Bạn muốn tùy chỉnh hành vi được cung cấp theo ConcreteClass1 theo mặc định. Tùy chỉnh bạn muốn đạt được thường có thể được mô tả bằng một số tham số. Chỉ cần chuyển các tham số này đến một phương thức đặc biệt của CustomizedClass1 (có thể được bảo vệ) và đặt tên là customize() tương ứng.

  3. Tùy chỉnh được thực hiện trong hàm tạo ConcreteClass1() có thể là bất kỳ, cụ thể là hành vi của cá thể lớp có thể được 'hoàn nguyên' thành số AbstractClass, mà bạn có thể yêu cầu (nếu tôi làm đúng).

  4. Calling customize_with() thực sự có thể giới thiệu một số chi phí phụ thuộc vào những thứ thực tế thực hiện trong ConcreteClass1(), trong trường hợp này bằng cách sử dụng quá tải (và có thể được bảo vệ) nhà xây dựng chắc chắn là một giải pháp tốt hơn, trừ khi bạn muốn ví dụ ConcreteClass1 'là động tùy biến (trong trường hợp này, customize_with() và có thể phần còn lại của ConcreteClass1 phải được thiết kế phù hợp, nghĩa là hỗ trợ hành vi đó theo hợp đồng).

Xin lỗi nếu có gì sai với cú pháp (Tôi chưa viết nhiều mã Java).

+0

Câu hỏi là để tránh thực hiện/* triển khai hữu ích */một phần của ConcreteClass1, tôi không nghĩ rằng giải pháp này đạt được nó. –

+0

@Kenneth Xu Có thể, nhưng, ngay cả những câu hỏi về cách làm-hack-xứng đáng xứng đáng với _some_ câu trả lời theo định hướng thực hành tốt hơn! Nói cách khác, giải pháp này cố gắng đạt được điều gì đó tốt hơn (hoặc ít nhất tôi nghĩ vậy :)). Cheeers! – mlvljr

7

Hai nhận xét: Thứ nhất, bạn không phải suy nghĩ về 'nhảy qua' nhà xây dựng như thế này. Thứ hai, nó thực sự là thực sự là âm thanh như bạn cần phải suy nghĩ lại mối quan hệ lớp học của bạn.

Bất cứ khi nào bạn thấy mình nghĩ "A mở rộng B, ngoại trừ điều đó ..." là thời điểm rất tốt để xem xét những thứ xa hơn. 'Mở rộng' ngụ ý 'là', là một trong hai hoặc mối quan hệ: có hành vi tùy chọn thêm các vùng màu xám sẽ cắn bạn sau này. Như mọi người đã nói, bạn có thể cung cấp nhiều hàm tạo trên ConcreteClass1 để thực hiện việc khởi tạo mà bạn yêu cầu trong mỗi trường hợp, có thể làm cho chúng được bảo vệ để chúng chỉ có thể được sử dụng bởi các lớp con. Nhưng đây là một câu hỏi: nếu ai đó muốn viết CustomizedClass2 cần một số (nhưng không phải tất cả) của các chức năng trong ConcreteClass1? Bạn có thêm một hàm tạo tùy chỉnh khác không?

+1

Tất cả những gì chúng ta đang nói đến ở đây là bỏ qua bất kỳ khởi tạo nào đang được thực hiện trong hàm tạo, không phải "một số (nhưng không phải tất cả) của chức năng". Đó sẽ là một vi phạm LSP. :-) – MattMcKnight

+0

Trong ví dụ của bạn, có thể tốt hơn là thiết kế (và xem) ConcreteClass1 dưới dạng lớp có thể tùy chỉnh 'swiss-knife' (có thể được thừa kế và tự khởi tạo) trước. Vì vậy, có, nếu bạn thực sự quên về các chức năng cần thiết (hoặc các yêu cầu đã thay đổi) và chắc chắn cần phải sử dụng chính xác lớp ConcreteClass1 làm cơ sở cho CustomizedClass2 - thêm các phương thức thích hợp (incl. Constructors) hoặc tùy chỉnh những cái hiện có. Đó có thể là một lý do đằng sau thiết kế của chủ đề khởi động. – mlvljr

0

Một cách tôi đến với:

class AbstractClass { 
    AbstractClass(){ init(); } 
    protected init(){ /* useful implementation */ } 
} 

class ConcreteClass1 extends AbstractClass { 
    ConcreteClass1(){ init(); /* useful implementation */ } 
} 

class CustomizedClass1 extends ConcreteClass1 { 
    CustomizedCLass1(){ init(); /* useful implementation */ } 
} 

Bằng cách này, CustomizedClass1 được hành vi cần thiết từ AbstractClass mà không đi qua khởi tạo ConcreteClass1.

EDIT: Ouch, điều này không hoạt động vì các hàm tạo của cha mẹ được ngầm gọi là một trong những người bình luận được chỉ ra, tôi nghĩ cách dễ dàng là có các nhà xây dựng khác nhau.

+2

Tính năng này hoạt động. Tôi nghĩ rằng rất nhiều câu trả lời ở đây là điên rồ. Tôi nghĩ rằng bạn có thể sử dụng cách tôi đề xuất là tốt, bằng cách có nhiều nhà thầu. Thường có những lúc bạn muốn tạo một đối tượng mà không cần chạy khởi tạo mặc định. Tôi không hiểu tại sao các poster được đánh giá cao nhất nghĩ rằng đó là một loại tội phạm. – MattMcKnight

+0

Bạn có thể đặt tên cho tên * điên * không? :))) – mlvljr

+0

@mlvjr Bạn không điên ... nó thực sự khá gần với câu trả lời này. Những người điên rồ là những người đưa anh chàng này đến củi để không hỏi câu hỏi của mình một cách rõ ràng để họ có thể chứng minh sự hiểu biết hướng đối tượng "mạnh mẽ" của họ. – MattMcKnight

1

Tùy chỉnhData1 có phải là phân lớp của ConcreteData1 không? Nếu vậy, sau đó tôi sẽ đề nghị có một (có thể bảo vệ) constructor cho ConcreteClass1 mà mất trong một ConcreteData1 để sử dụng thay vì lấy riêng của mình trong quá trình khởi tạo. Bằng cách này, CustomizedClass1 có thể lấy CustomizedData1 của nó và chuyển nó đến cuộc gọi của nó đến siêu. Thật không may, điều này có thể phức tạp hoặc không thể thực hiện được nếu bạn không thể tìm nạp dữ liệu trước một số init nội bộ.


class ConcreteClass1 extends AbstractClass { 
    ConcreteClass1(){ 
      this(...fetch data as before...); 
    } 
    ConcreteClass1(ConcreteData1 data){ 
      myData = data; 
      ... 
    } 
} 
class CustomizedClass1 extends ConcreteClass1 { 
    CustomizedCLass1(){ 
      super(new CustomizedData1(...)); 
      ... 
    } 
} 

Nhưng sau đó CustomizedClass1 có thể cần tham chiếu đến dữ liệu dưới dạng CustomizedData1 không chỉ đơn thuần là ConcreteData1. Nó chỉ có thể định kiểu ConcreteData1 được thừa kế của nó mọi lúc, nhưng điều đó có vẻ yucky. Nhưng nếu nó lưu trữ tham chiếu của riêng nó vào dữ liệu, thì cần phải giữ cho các tham chiếu đồng bộ nếu chúng không phải là cuối cùng.

0

Có, bạn có thể tinh chỉnh! Thêm một phương thức khác vào lớp cha (B) và trong lớp Con của bạn (C) gọi nó là super.executeParentA(); trong này super.execute gọi phương thức()

A --> execute() 

B --> executeParent_A() { super.execute(); } 

C --> super.executeParent_A(); 

-Mehboob

0

tôi có thể nghĩ đến hai trường hợp người ta có thể muốn làm điều này (trên thực tế không):

Trường hợp 1, ConcreteClass2 chạy chia sẻ initialisation từ lớp trên cùng, nhưng sau đó trình tự khởi tạo riêng của nó, mà là khác nhau/xung đột hơn một trong ConcreteClass1 -> có một phương thức init() và ghi đè nó (thay vì cố gắng ghi đè lên constructor của ConcreteClass1).

Trường hợp 2, bạn phải khởi động đa hình của một (hoặc nhiều) thành viên lớp (điều này thực sự là một trường hợp cụ thể của tuần trước):

public class ConcreteClass1 extend AbstractClass 
{ 
    protected F1 f; 

    public ConcreteClass1() { 
    super(); 
    this.f = new F1(); 
    } 
} 

public ConcreteClass2 extends ConcreteClass1 
{ 
    public ConcreteClass2() { 
    super(); // I'd like to do super.super() instead (no, you don't) 
    this.f = new F2(); // We wasted time with new F1(); 
    } 
} 

Trong trường hợp như vậy, hoặc là sử dụng phương pháp init() , hoặc làm điều này:

protected ConcreteClass1 (F1 f) { 
    super(); 
    this.f = f; 
} 
public ConcreteClass1() { 
    this (new F1()); 
} 

... 

public ConcreteClass2() { 
    super (new F2()); 
} 

Nói cách khác, làm cho lý do cho sự nhảy rõ ràng, đối với ngầm nhảy qua hệ thống phân cấp bị cấm và vì những lý do tốt giải thích trong câu trả lời khác.

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