2013-07-30 54 views
10

Tôi đang mắc kẹt trong tình huống này ở đâu:Có thể ghi đè lên phương thức có tham số bắt nguồn thay vì tham số cơ sở không?

  1. Tôi có một lớp trừu tượng gọi là Ammo, với AmmoBoxClip như trẻ em.
  2. Tôi có một lớp trừu tượng có tên là Weapon, với FirearmMelee là trẻ em.
  3. Firearm là trừu tượng, với ClipWeaponShellWeapon là trẻ em.
  4. Bên Firearm, có một void Reload(Ammo ammo);

Vấn đề là, một ClipWeapon có thể sử dụng cả một Clip và một AmmoBox để tải lại:

public override void Reload(Ammo ammo) 
{ 
    if (ammo is Clip) 
    { 
     SwapClips(ammo as Clip); 
    } 
    else if (ammo is AmmoBox) 
    { 
     var ammoBox = ammo as AmmoBox; 
     // AddBullets returns how many bullets has left from its parameter 
     ammoBox.Set(clip.AddBullets(ammoBox.nBullets)); 
    } 
} 

Nhưng một ShellWeapon, chỉ có thể sử dụng một AmmoBox để tải lại. Tôi có thể làm điều này:

public override void Reload(Ammo ammo) 
{ 
    if (ammo is AmmoBox) 
    { 
     // reload... 
    } 
} 

Nhưng điều này là xấu bởi vì, mặc dù tôi đang kiểm tra để chắc chắn rằng nó là loại AmmoBox, từ bên ngoài, nó xuất hiện như một ShellWeapon có thể mất một Clip là tốt, vì một Clip cũng là Ammo.

Hoặc, tôi có thể loại bỏ Reload từ Firearm, và đặt nó cả ClipWeaponShellWeapon với params cụ thể tôi cần, nhưng làm như vậy tôi sẽ mất những lợi ích của Polymorphism, mà không phải là những gì tôi muốn.

Nó sẽ không được tối ưu, nếu tôi có thể ghi đè lên Reload bên ShellWeapon như thế này:

public override void Reload(AmmoBox ammoBox) 
{ 
    // reload ... 
} 

Tất nhiên tôi đã thử nó, và nó đã không làm việc, tôi nhận được một lỗi nói rằng chữ ký phải phù hợp hoặc cái gì đó, nhưng điều này không hợp lệ 'hợp lý'? kể từ AmmoBoxAmmo?

Tôi nên làm như thế nào? Và nói chung, thiết kế của tôi có đúng không? (Lưu ý tôi đã sử dụng giao diện IClipWeaponIShellWeapon nhưng tôi gặp sự cố, vì vậy tôi đã chuyển sang sử dụng các lớp học)

Cảm ơn bạn trước.

Trả lời

13

but shouldn't this be valid 'logically'?

số giao diện của bạn nói rằng người gọi có thể vượt qua trong bất kỳAmmo - nơi bạn đang hạn chế nó để yêu cầu một AmmoBox, đó là cụ thể hơn.

gì bạn mong đợi xảy ra nếu ai đó viết:

Firearm firearm = new ShellWeapon(); 
firearm.Reload(new Ammo()); 

? Đó phải là mã hoàn toàn hợp lệ - vậy bạn có muốn nó nổ tung vào thời gian thực hiện không? Một nửa điểm đánh máy tĩnh là tránh loại vấn đề đó.

Bạn thể làm Firearm chung trong các loại đạn là sử dụng:

public abstract class Firearm<TAmmo> : Weapon where TAmmo : Ammo 
{ 
    public abstract void Reload(TAmmo ammo); 
} 

Sau đó:

public class ShellWeapon : Firearm<AmmoBox> 

Đó có thể hoặc không thể là một cách hữu hiệu để làm việc, nhưng nó ít nhất đáng xem xét.

+0

Làm thế nào có thể viết lại định nghĩa chung chung bạn đã viết nhưng đối với Báng súng thay vì vũ khí? (vì Shell và Clip vũ khí kế thừa từ Súng và Vũ khí trực tiếp) Tôi đã thử một cái gì đó như thế này nhưng rõ ràng đó không phải là: public abstract class Firearm nơi AmmoType: Ammo: Weapon XD – vexe

+0

@VeXe: Bạn cần phải đặt ': Weapon 'trước' AmmoType: Ammo'. Tôi sẽ cập nhật câu trả lời của tôi để hiển thị điều này. Tôi sẽ * mạnh mẽ * khuyên bạn sử dụng tiền tố 'T' cho các tham số kiểu mặc dù. –

+0

Tôi nghĩ rằng điều này là tốt nhất, nhưng tôi có thể hỏi tại sao bạn nói nó có thể không phải là một cách hữu ích để làm việc? – vexe

1

Tôi nghĩ rằng, thật tuyệt khi kiểm tra, cho dù thông qua Ammo có thuộc loại hợp lệ hay không. Tình huống tương tự là, khi hàm chấp nhận một Stream, nhưng kiểm tra nội bộ, cho dù đó là tìm kiếm hoặc có thể ghi - tùy theo yêu cầu của nó.

3

Vấn đề mà bạn đang vật lộn xuất phát từ sự cần thiết phải gọi một triển khai khác nhau dựa trên các loại thời gian chạy của cả đạn dược vũ khí. Về cơ bản, hành động tải lại cần phải là "ảo" đối với hai, không phải là một đối tượng. Vấn đề này được gọi là double dispatch.

Một cách để giải quyết nó sẽ được tạo ra một cấu trúc khách truy cập như:

abstract class Ammo { 
    public virtual void AddToShellWeapon(ShellWeapon weapon) { 
     throw new ApplicationException("Ammo cannot be added to shell weapon."); 
    } 
    public virtual void AddToClipWeapon(ClipWeapon weapon) { 
     throw new ApplicationException("Ammo cannot be added to clip weapon."); 
    } 
} 
class AmmoBox : Ammo { 
    public override void AddToShellWeapon(ShellWeapon weapon) { 
     ... 
    } 
    public override void AddToClipWeapon(ClipWeapon weapon) { 
     ... 
    } 
} 
class Clip : Ammo { 
    public override void AddToClipWeapon(ClipWeapon weapon) { 
     ... 
    } 
} 
abstract class Weapon { 
    public abstract void Reload(Ammo ammo); 
} 
class ShellWeapon : Weapon { 
    public void Reload(Ammo ammo) { 
     ammo.AddToShellWeapon(this); 
    } 
} 
class ClipWeapon : Weapon { 
    public void Reload(Ammo ammo) { 
     ammo.AddToClipWeapon(this); 
    } 
} 

"Sự kỳ diệu" xảy ra trong hiện thực của Reload của lớp con vũ khí: chứ không phải quyết định những loại đạn họ nhận được, chúng cho phép đạn tự làm "chân thứ hai" của công văn kép, và gọi phương thức nào là thích hợp, bởi vì các phương thức AddTo...Weapon của chúng ta biết cả loại riêng của chúng và loại vũ khí mà chúng được nạp lại.

+0

Vấn đề là, vì cả AddToShellWeapon và AddToClipWeapon đều là trừu tượng, chúng phải được định nghĩa trong Ammo kế thừa của bất kỳ ai, vì vậy bên trong Clip có AddToShellWeapon, là thừa. Đúng nếu tôi sai. – vexe

+0

@VeXe Bạn không cần phải làm cho chúng trừu tượng - bạn có thể biến chúng thành ảo, và để chúng ném một ngoại lệ theo mặc định, nói rằng "loại amo này không thể được thêm vào loại vũ khí đó". Tôi đã chỉnh sửa câu trả lời để minh họa cách tiếp cận. – dasblinkenlight

+0

Đó là một ý tưởng hay, theo cách khác, nhưng nếu tôi làm thế, thì tại sao tôi không dùng giải pháp đầu tiên và thêm một người khác nếu (đạn là Clip) -> ném ngoại lệ? Phương pháp của bạn cung cấp nhiều gì hơn, vì vậy tôi sẽ xem xét nó thay vì phương pháp của tôi? – vexe

2

Bạn có thể sử dụng thành phần với phần mở rộng giao diện thay vì nhiều-thừa kế:

class Ammo {} 
class Clip : Ammo {} 
class AmmoBox : Ammo {} 

class Firearm {} 
interface IClipReloadable {} 
interface IAmmoBoxReloadable {} 

class ClipWeapon : Firearm, IClipReloadable, IAmmoBoxReloadable {} 
class AmmoBoxWeapon : Firearm, IAmmoBoxReloadable {} 

static class IClipReloadExtension { 
    public static void Reload(this IClipReloadable firearm, Clip ammo) {} 
} 

static class IAmmoBoxReloadExtension { 
    public static void Reload(this IAmmoBoxReloadable firearm, AmmoBox ammo) {} 
} 

Vì vậy mà bạn sẽ có 2 định nghĩa của phương pháp với Clip và AmmoBox như các đối số trong ClipWeapon và chỉ có 1 Nạp lại Nạp lại()() phương pháp trong lớp AmmoBoxWeapon với đối số AmmoBox.

var ammoBox = new AmmoBox(); 
var clip = new Clip(); 

var clipWeapon = new ClipWeapon(); 
clipWeapon.Reload(ammoBox); 
clipWeapon.Reload(clip); 

var ammoBoxWeapon = new AmmoBoxWeapon(); 
ammoBoxWeapon.Reload(ammoBox); 

Và nếu bạn cố gắng vượt qua Clip để AmmoBoxWeapon.Reload bạn sẽ nhận được một lỗi:

ammoBoxWeapon.Reload(clip); // <- ERROR at compile time 
+0

Đây là chính xác những gì tôi đang cố gắng làm, nhưng cách bạn đang làm nó là một chút ngoài kiến ​​thức của tôi. Tôi đã không thực sự có được những gì với các phần mở rộng, và những gì 'này' đang làm ... có lẽ tôi đi kiểm tra một số hướng dẫn về phần mở rộng giao diện này và hãy xem giải pháp của bạn một lần nữa. Những gì được cho là phù hợp với các giao diện? Xin lỗi nhưng hiện tại tôi hơi mờ. – vexe

+0

@VeXe Nếu bạn chỉ cần Nạp lại phương thức, bạn có thể để trống giao diện, phần mở rộng sẽ làm tất cả những gì bạn cần. Hãy xem chúng là tính năng khá hữu ích của C# để mở rộng chức năng của các lớp. –

+0

@Rinold đây là đẹp, cảm ơn bạn – user25064