2016-03-22 17 views
7

Giả sử chúng ta có giao diện đồ chơi sau:Java thực hành tốt nhất: đúc tượng vs giao diện

interface Speakable 
{ 
    public abstract void Speak(); 
} 

interface Flyer 
{ 
    public abstract void Fly(); 
} 

và chúng tôi có lớp mà thực hiện cả hai giao diện:

class Duck implements Speakable, Flyer 
{ 
    public void Speak() 
    { 
     System.out.println("quack quack don't eat me I taste bad."); 
    } 

    public void Fly() 
    { 
     System.out.println("I am flying"); 
    } 
} 

Tại thời điểm này tôi thấy nhiều cách khác nhau để gọi các phương thức trên Duck và tôi không thể quyết định phương pháp nào là phương pháp hay nhất. Hãy xem xét kịch bản này:

public class Lab 
{ 
     private static void DangerousSpeakAndFly(Object x) 
     { 
      Speakable temp = (Speakable) x; 
      temp.Speak(); 
      Flyer temp2= (Flyer) x; 
      temp2.Fly(); 
     } 

     public static void main(String[] args) 
     { 
      Duck daffy= new Duck(); 
      DangerousSpeakAndFly(daffy); 
     } 
} 

Chương trình này sẽ cư xử như mong đợi, bởi vì đối tượng được truyền vào chức năng sẽ xảy ra là cast để FlyerSpeakable, nhưng tôi co rúm người lại khi tôi nhìn thấy mã như thế này vì nó không cho phép biên dịch kiểm tra kiểu thời gian và do khớp nối chặt chẽ nó có thể ném ngoại lệ bất ngờ ví dụ khi một đối tượng được nhập khác (không thể chuyển sang một trong hai hoặc một trong các giao diện) được chuyển thành tham số hoặc nếu thực hiện Duck thay đổi xuống dòng. thực hiện dài hơn Flyer.

Tôi thấy mã Java được viết như thế này mọi lúc, đôi khi trong sách giáo khoa (ví dụ: pg. 300 của "Mẫu thiết kế đầu tiên" bởi O'Reilly) vì vậy phải có một bằng khen trong đó tôi thiếu.

Nếu tôi viết mã tương tự, tôi sẽ cố gắng tránh downcasting sang một loại hoặc giao diện không được bảo đảm. ví dụ trong trường hợp này tôi sẽ làm một cái gì đó như thế này:

interface SpeakingFlyer extends Flyer, Speakable 
{ 

} 

class BuzzLightyear implements SpeakingFlyer 
{ 
    public void Speak() 
    { 
     System.out.println("My name is Buzz"); 
    } 
    public void Fly() 
    { 
     System.out.println("To infinity and beyond!"); 
    } 
} 

Trong đó sẽ cho phép tôi làm:

private static void SafeSpeakAndFly(SpeakingFlyer x) 
{ 
    x.Speak(); 
    x.Fly(); 
} 

public static void main(String[] args) 
{ 
    BuzzLightyear bly= new BuzzLightyear(); 
    SafeSpeakAndFly(bly); 
} 

Đây có phải là một quá mức cần thiết không cần thiết? những cạm bẫy để làm điều này là gì?

Tôi cảm thấy thiết kế này tách chức năng SafeSpeakAndFly() khỏi các tham số của nó và giữ các lỗi khó chịu khi bay do kiểm tra loại thời gian biên dịch.

Tại sao phương pháp đầu tiên được sử dụng rộng rãi trong thực tế và phương pháp sau không được sử dụng?

+0

Tôi tự làm cho mình những câu hỏi giống nhau năm trước và bài đăng này ở đây là câu trả lời ... http://stackoverflow.com/a/384067/982161 –

Trả lời

11

Tôi thấy mã Java được viết như thế này mọi lúc, đôi khi trong sách giáo khoa (ví dụ: pg. 300 của "Mẫu thiết kế đầu tiên" bởi O'Reilly) vì vậy phải có bằng khen trong đó .

Cuốn sách này được xuất bản lần đầu vào năm 2004 và tôi không nghĩ Java hỗ trợ Generics tại thời điểm đó. Vì vậy, không an toàn đúc là một cái gì đó đã được rất phổ biến được sử dụng sau đó.Có lẽ, nếu tôi không có sự hỗ trợ của đa hình tham số trong Java, trước tiên tôi sẽ kiểm tra xem tham số có phải là một thể hiện của loại tôi muốn truyền cho và sau đó thực hiện dàn diễn viên thực tế:

private static void dangerousSpeakAndFly(Object x) { 
    if (x instanceof Speakable) { 
     Speakable temp = (Speakable) x; 
     temp.Speak(); 
    } 
    if (x instanceof Flyer) { 
     Flyer temp2= (Flyer) x; 
     temp2.Fly(); 
    } 
} 

có Generics, tuy nhiên, cho phép chúng ta làm điều này:

private static <T extends Speakable & Flyer> void reallySafeSpeakAndFly(T x) { 
    x.Speak(); 
    x.Fly(); 
} 

ở đây, trình biên dịch có thể chắc chắn rằng chúng ta sẽ không đi qua một cái gì đó mà không thực hiện SpeakableFlyer và có thể phát hiện như vậy hỗn xược nỗ lực tại thời gian biên dịch.

Tại sao phương pháp đầu tiên được sử dụng rộng rãi trong thực tế và phương pháp sau không được sử dụng?

Có thể bạn đã thấy rất nhiều mã cũ, tôi cho là vậy. :)

+2

rất thông tin và có tính hướng dẫn. Và vâng, sửa mã di sản bị hỏng = câu chuyện về cuộc sống của tôi – ForeverStudent

7

Bạn có thể thực thi các lập luận để được cùng lúc SpeakableFlyer thực hiện một phương pháp chung với loại giao lộ:

private <T extends Speakable & Flyer> static void DangerousSpeakAndFly(T x) { 
    // use any of `Speakable` or `Flyer` methods of `x` 
} 

do đó bạn không cần phải đúc cũng không tạo ra giao diện bổ sung.

+0

giải thích tuyệt vời +1 câu trả lời đầu tiên – ForeverStudent

+0

bên cạnh đó, tôi khuyên bạn nên để chấp nhận câu trả lời @Konstantin càng nhiều thông tin và giải thích đơn hàng –

+1

bị ràng buộc – ForeverStudent

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