Cho một đàn ngựa, làm thế nào để tôi tìm thấy chiều dài sừng trung bình của tất cả các con kỳ lân?


30

Câu hỏi ở trên là một ví dụ trừu tượng về một vấn đề phổ biến mà tôi gặp phải trong mã kế thừa, hay chính xác hơn là các vấn đề phát sinh từ các nỗ lực trước đây trong việc giải quyết vấn đề này.

Tôi có thể nghĩ về ít nhất một phương thức .NET framework nhằm giải quyết vấn đề này, như Enumerable.OfType<T>phương thức này. Nhưng thực tế là cuối cùng bạn lại thẩm vấn kiểu của một đối tượng trong thời gian chạy không phù hợp với tôi.

Ngoài việc hỏi mỗi con ngựa "Bạn có phải là một con kỳ lân?" Các cách tiếp cận sau đây cũng xuất hiện trong tâm trí:

  • Ném một ngoại lệ khi một nỗ lực được thực hiện để có được chiều dài của sừng của một con kỳ lân (phơi bày chức năng không phù hợp với mỗi con ngựa)
  • Trả về giá trị mặc định hoặc ma thuật cho độ dài của sừng của một con kỳ lân (yêu cầu kiểm tra mặc định được tiêu trong bất kỳ mã nào muốn phá vỡ các chỉ số sừng trên một nhóm ngựa mà tất cả đều không phải là kỳ lân)
  • Tránh xa sự kế thừa và tạo một vật thể riêng biệt trên một con ngựa cho bạn biết con ngựa đó có phải là một con kỳ lân hay không (có khả năng đẩy vấn đề tương tự xuống một lớp)

Tôi có cảm giác điều này sẽ được trả lời tốt nhất với một "câu trả lời không". Nhưng làm thế nào để bạn tiếp cận vấn đề này và nếu nó phụ thuộc, bối cảnh xung quanh quyết định của bạn là gì?

Tôi cũng quan tâm đến bất kỳ hiểu biết nào về việc vấn đề này có còn tồn tại trong mã chức năng không (hoặc có thể nó chỉ tồn tại trong các ngôn ngữ chức năng hỗ trợ khả năng biến đổi?)

Điều này đã được gắn cờ là một bản sao có thể có của câu hỏi sau: Làm thế nào để tránh bị hạ thấp?

Câu trả lời cho câu hỏi đó giả định rằng người ta đang sở hữu một HornMeasurercái mà tất cả các phép đo sừng phải được thực hiện. Nhưng đó hoàn toàn là một sự áp đặt đối với một cơ sở mã được hình thành theo nguyên tắc bình đẳng rằng mọi người nên được tự do đo sừng của một con ngựa.

Vắng mặt a HornMeasurer, cách tiếp cận của câu trả lời được chấp nhận phản ánh cách tiếp cận dựa trên ngoại lệ được liệt kê ở trên.

Cũng có một số nhầm lẫn trong các ý kiến ​​về việc ngựa và kỳ lân đều là ngựa hay là một con kỳ lân là một phân loài ma thuật của ngựa. Cả hai khả năng nên được xem xét - có lẽ cái này thích hơn cái kia?


22
Ngựa không có sừng, vì vậy trung bình là không xác định (0/0).
Scott Whitlock

3
@moarboiler khắc Bất cứ nơi nào từ 10 đến vô cùng.
mẫu

4
@StephenP: Điều đó sẽ không hoạt động toán học trong trường hợp này; tất cả những số 0 sẽ làm lệch trung bình.
Mason Wheeler

3
Nếu câu hỏi của bạn được trả lời tốt nhất với câu trả lời không trả lời, thì nó không thuộc về trang web Hỏi & Đáp; reddit, quora hoặc các trang web dựa trên thảo luận khác được xây dựng cho các loại công cụ không trả lời ... điều đó nói rằng, tôi nghĩ rằng nó có thể trả lời rõ ràng nếu bạn đang tìm mã @MasonWheeler đưa ra, nếu không tôi nghĩ tôi không biết những gì bạn đang cố gắng hỏi ..
Jimmy Hoffa

3
@JimmyHoffa "bạn đang làm sai" tình cờ là "không trả lời" có thể chấp nhận được và thường thì tốt hơn "tốt, đây là một cách bạn có thể làm" - không cần thảo luận mở rộng.
moarboiler khắc

Câu trả lời:


11

Giả sử bạn muốn coi một Unicornloại đặc biệt Horse, về cơ bản có hai cách bạn có thể mô hình hóa nó. Cách truyền thống hơn là mối quan hệ lớp con. Bạn có thể tránh kiểm tra loại và hạ lưu bằng cách đơn giản cấu trúc lại mã của mình để luôn giữ các danh sách riêng biệt trong bối cảnh nơi nó quan trọng và chỉ kết hợp chúng trong bối cảnh mà bạn không bao giờ quan tâm đến Unicorncác đặc điểm. Nói cách khác, bạn sắp xếp nó để bạn không bao giờ gặp phải tình huống cần trích xuất con kỳ lân từ một đàn ngựa ngay từ đầu. Điều này có vẻ khó khăn lúc đầu, nhưng có thể xảy ra trong 99,99% trường hợp và thường thực sự làm cho mã của bạn sạch hơn rất nhiều.

Một cách khác mà bạn có thể mô hình một con kỳ lân chỉ bằng cách cho tất cả các con ngựa có chiều dài sừng tùy chọn. Sau đó, bạn có thể kiểm tra xem đó có phải là một con kỳ lân hay không bằng cách kiểm tra xem nó có chiều dài sừng hay không và tìm chiều dài sừng trung bình của tất cả các con kỳ lân bằng (trong Scala):

case class Horse(val hornLength: Option[Double])

val horse = Horse(None)
val unicorn = Horse(Some(12.0))
val anotherUnicorn = Horse(Some(6.0))

val herd = List(horse, unicorn, anotherUnicorn)
val hornLengths = herd flatMap {_.hornLength}
val averageLength = hornLengths.sum / hornLengths.size

Phương pháp này có ưu điểm là đơn giản hơn, với một lớp duy nhất, nhưng nhược điểm là ít mở rộng hơn và có cách kiểm tra "đường vòng". Mẹo nếu bạn đi với giải pháp này là nhận ra khi nào bạn bắt đầu mở rộng nó thường xuyên rằng bạn cần chuyển sang kiến ​​trúc linh hoạt hơn. Loại giải pháp này phổ biến hơn nhiều trong các ngôn ngữ chức năng nơi bạn có các chức năng đơn giản và mạnh mẽ như flatMapdễ dàng lọc ra các Nonemục.


7
Tất nhiên, điều này giả định rằng sự khác biệt duy nhất giữa một con ngựa bình thường và một con kỳ lân là sừng. Nếu đây không phải là trường hợp, thì mọi thứ sẽ phức tạp hơn rất nhiều rất nhanh.
Mason Wheeler

@MasonWheeler chỉ trong phương pháp thứ hai được trình bày.
moarboiler khắc

1
Phát hiện với các ý kiến ​​về cách những con kỳ lân và kỳ lân không bao giờ nên được viết cùng nhau trong một kịch bản thừa kế cho đến khi bạn ở trong một bối cảnh mà bạn không quan tâm đến kỳ lân. Chắc chắn, .OfType () có thể giải quyết vấn đề và khiến mọi thứ hoạt động, nhưng nó giải quyết vấn đề thậm chí không tồn tại ngay từ đầu. Đối với cách tiếp cận thứ hai, nó hoạt động vì các tùy chọn vượt trội hơn nhiều so với việc dựa vào null để ngụ ý một cái gì đó. Tôi nghĩ rằng cách tiếp cận thứ hai có thể đạt được trong OO với sự thỏa hiệp nếu bạn gói gọn các đặc điểm kỳ lân trong một tài sản độc lập và cực kỳ cảnh giác.
moarboiler khắc

1
thỏa hiệp nếu bạn gói gọn các đặc điểm kỳ lân trong một tài sản độc lập và cực kỳ cảnh giác - tại sao làm cho cuộc sống khó khăn cho chính mình. Sử dụng typeof trực tiếp và tiết kiệm rất nhiều vấn đề trong tương lai.
gbjbaanb

@gbjbaanb Tôi sẽ xem xét phương pháp đó chỉ thực sự phù hợp với các tình huống thiếu máu Horsecó một IsUnicorntài sản và một loại UnicornStufftài sản nào đó có chiều dài sừng trên đó (khi chia tỷ lệ cho người lái / long lanh được đề cập trong câu hỏi của bạn).
moarboiler khắc

38

Bạn đã bao gồm khá nhiều tùy chọn. Nếu bạn có hành vi phụ thuộc vào một kiểu con cụ thể và nó bị trộn lẫn với các kiểu khác, mã của bạn phải được biết về kiểu con đó; đó là lý luận logic đơn giản.

Cá nhân, tôi chỉ cần đi với horses.OfType<Unicorn>().Average(u => u.HornLength). Nó thể hiện ý định của mã rất rõ ràng, điều này thường là điều quan trọng nhất vì cuối cùng ai đó sẽ phải duy trì nó sau này.


Xin vui lòng tha thứ cho tôi nếu cú ​​pháp lambda của tôi không đúng; Tôi không phải là một lập trình viên C # nhiều và tôi không bao giờ có thể giữ các chi tiết phức tạp như thế này. Nó nên rõ ràng những gì tôi có ý nghĩa, mặc dù.
Mason Wheeler

1
Đừng lo lắng, vấn đề được giải quyết khá nhiều khi danh sách chỉ chứa Unicorns dù sao (đối với bản ghi bạn có thể bỏ qua return).
moarboiler khắc

4
Đây là câu trả lời tôi sẽ đi nếu tôi muốn giải quyết vấn đề nhanh chóng. Nhưng không phải là câu trả lời nếu tôi muốn cấu trúc lại mã để hợp lý hơn.
Andy

6
Đây chắc chắn là câu trả lời trừ khi bạn cần một số mức tối ưu hóa vô lý. Sự rõ ràng và dễ đọc của nó làm cho khá nhiều thứ khác phải di chuyển.
David nói Phục hồi

1
@DavidGrinberg điều gì xảy ra nếu viết phương pháp sạch, dễ đọc này có nghĩa là trước tiên bạn sẽ phải thực hiện cấu trúc thừa kế mà trước đây không tồn tại?
moarboiler khắc

9

Không có gì sai trong .NET với:

var unicorn = animal as Unicorn;
if(unicorn != null)
{
    sum += unicorn.HornLength;
    count++;
}

Sử dụng tương đương Linq cũng tốt:

var averageUnicornHornLength = animals
    .OfType<Unicorn>()
    .Select(x => x.HornLength)
    .Average();

Dựa trên câu hỏi bạn đã hỏi trong tiêu đề, đây là mã tôi sẽ tìm thấy. Nếu câu hỏi hỏi một cái gì đó như "mức trung bình của động vật có sừng" thì sẽ khác:

var averageHornedAnimalHornLength = animals
    .OfType<IHornedAnimal>()
    .Select(x => x.HornLength)
    .Average();

Lưu ý rằng khi sử dụng Linq, Average(và MinMax) sẽ đưa ra một ngoại lệ nếu liệt kê trống và loại T không thể rỗng. Đó là bởi vì mức trung bình thực sự không xác định (0/0). Vì vậy, thực sự bạn cần một cái gì đó như thế này:

var hornedAnimals = animals
    .OfType<IHornedAnimal>()
    .ToList();
if(hornedAnimals.Count > 0)
{
    var averageHornLengthOfHornedAnimals = hornedAnimals
        .Average(x => x.HornLength);
}
else
{
    // deal with it in your own way...
}

Chỉnh sửa

Tôi chỉ nghĩ rằng điều này cần thêm ... một trong những lý do khiến một câu hỏi như thế này không phù hợp với các lập trình viên hướng đối tượng là nó giả định rằng chúng ta đang sử dụng các lớp và các đối tượng để mô hình hóa cấu trúc dữ liệu. Ý tưởng định hướng đối tượng Smalltalk-esque ban đầu là cấu trúc chương trình của bạn ra khỏi các mô-đun được khởi tạo dưới dạng đối tượng và thực hiện các dịch vụ cho bạn khi bạn gửi tin nhắn cho chúng. Việc chúng ta cũng có thể sử dụng các lớp và các đối tượng để mô hình hóa cấu trúc dữ liệu là một tác dụng phụ (hữu ích), nhưng chúng là hai thứ khác nhau. Tôi thậm chí không nghĩ rằng cái sau nên được coi là lập trình hướng đối tượng, vì bạn có thể làm điều tương tự với a struct, nhưng nó sẽ không đẹp như vậy.

Nếu bạn đang sử dụng lập trình hướng đối tượng để tạo ra các dịch vụ làm việc cho bạn, thì việc truy vấn xem dịch vụ đó có thực sự là một số dịch vụ khác hay việc triển khai cụ thể thường không được chấp nhận vì lý do chính đáng. Bạn đã được cung cấp một giao diện (thường thông qua tiêm phụ thuộc) và bạn nên mã cho giao diện / hợp đồng đó.

Mặt khác, nếu bạn (sử dụng sai) các ý tưởng lớp / đối tượng / giao diện để tạo cấu trúc dữ liệu hoặc mô hình dữ liệu, thì cá nhân tôi không thấy vấn đề gì khi sử dụng ý tưởng đầy đủ. Nếu bạn đã xác định rằng kỳ lân là một loại ngựa con và nó hoàn toàn có ý nghĩa trong miền của bạn, thì hãy hoàn toàn tiếp tục và truy vấn những con ngựa trong đàn của bạn để tìm những con kỳ lân. Xét cho cùng, trong trường hợp như thế này, chúng tôi thường cố gắng tạo một ngôn ngữ cụ thể cho miền để thể hiện tốt hơn các giải pháp cho các vấn đề chúng tôi phải giải quyết. Theo nghĩa đó, không có gì sai với .OfType<Unicorn>()vv

Cuối cùng, lấy một bộ sưu tập các mục và lọc nó theo kiểu thực sự chỉ là lập trình chức năng, không phải lập trình hướng đối tượng. Rất may, các ngôn ngữ như C # đang thoải mái xử lý cả hai mô hình.


7
Bạn đã biết đó animal là một Unicorn ; chỉ sử dụng thay vì sử dụng as, hoặc có khả năng sử dụng tốt hơn as và sau đó kiểm tra null.
Philip Kendall

3

Nhưng thực tế là cuối cùng bạn lại thẩm vấn kiểu của một đối tượng trong thời gian chạy không phù hợp với tôi.

Vấn đề với tuyên bố này là, cho dù bạn sử dụng cơ chế nào, bạn sẽ luôn thẩm vấn đối tượng để cho biết đó là loại gì. Đó có thể là RTTI hoặc nó có thể là một liên minh hoặc cấu trúc dữ liệu đơn giản nơi bạn yêu cầu if horn > 0. Các chi tiết cụ thể chính xác thay đổi một chút nhưng ý định là như nhau - bạn hỏi đối tượng về bản thân theo một cách nào đó để xem liệu bạn có nên thẩm vấn thêm không.

Do đó, thật hợp lý khi sử dụng sự hỗ trợ ngôn ngữ của bạn để làm điều này. Trong .NET bạn sử dụng typeofchẳng hạn.

Lý do để làm điều này vượt xa chỉ là sử dụng ngôn ngữ của bạn tốt. Nếu bạn có một đối tượng trông giống như một đối tượng khác nhưng với một số thay đổi nhỏ, rất có thể bạn sẽ tìm thấy nhiều sự khác biệt hơn theo thời gian. Trong ví dụ của bạn về kỳ lân / ngựa, bạn có thể nói rằng chỉ có chiều dài sừng ... nhưng ngày mai bạn sẽ kiểm tra xem một tay đua tiềm năng có phải là một trinh nữ hay không, nếu phân của nó là long lanh. (một ví dụ trong thế giới thực cổ điển sẽ là các widget GUI xuất phát từ một cơ sở chung và bạn phải tìm các hộp kiểm và hộp danh sách khác nhau. Số lượng khác biệt sẽ quá lớn để tạo ra một siêu đối tượng duy nhất có thể chứa tất cả các hoán vị dữ liệu có thể có ).

Nếu việc kiểm tra loại đối tượng trong thời gian chạy không giữ được tốt, thì phương án thay thế của bạn là tách các đối tượng khác nhau ngay từ đầu - thay vì lưu trữ một đàn kỳ lân / ngựa, bạn giữ 2 bộ sưu tập - một cho ngựa, một cho kỳ lân . Điều này có thể hoạt động rất tốt, ngay cả khi bạn lưu trữ chúng trong một thùng chứa chuyên dụng (ví dụ: multimap trong đó khóa là loại đối tượng ... nhưng sau đó mặc dù chúng tôi lưu trữ chúng thành 2 nhóm, chúng tôi quay lại hỏi về loại đối tượng !)

Chắc chắn một cách tiếp cận dựa trên ngoại lệ là sai. Sử dụng ngoại lệ như luồng chương trình bình thường là mùi mã (nếu bạn có một đàn kỳ lân và một con lừa có vỏ sò đâm vào đầu nó, thì tôi sẽ nói cách tiếp cận dựa trên ngoại lệ là ổn, nhưng nếu bạn có một con kỳ lân và ngựa sau đó kiểm tra từng con cho kỳ lân không phải là bất ngờ. Ngoại lệ là cho các trường hợp đặc biệt, không phải là một iftuyên bố phức tạp ). Trong mọi trường hợp, sử dụng ngoại lệ cho vấn đề này chỉ đơn giản là thẩm vấn loại đối tượng khi chạy, chỉ ở đây bạn đang sử dụng sai tính năng ngôn ngữ để kiểm tra các đối tượng không phải là kỳ lân. Bạn cũng có thể mã trong mộtif horn > 0 và ít nhất là xử lý bộ sưu tập của bạn một cách nhanh chóng, rõ ràng, sử dụng ít dòng mã hơn và tránh mọi vấn đề phát sinh từ khi các ngoại lệ khác được đưa ra (ví dụ: một bộ sưu tập trống hoặc cố gắng đo vỏ sò của con lừa đó)


Trong một bối cảnh di sản, ban đầu if horn > 0khá nhiều cách giải quyết vấn đề này. Sau đó, các vấn đề thường xuất hiện là khi bạn muốn kiểm tra người lái và long lanh, và horn > 0bị chôn vùi khắp nơi trong mã không liên quan (cũng là mã bị lỗi bí ẩn do thiếu kiểm tra khi còi bằng 0). Ngoài ra, con ngựa phân lớp sau thực tế thường là đề xuất đắt nhất, vì vậy tôi thường không có xu hướng làm điều đó nếu chúng vẫn được viết cùng nhau ở cuối bộ tái cấu trúc. Vì vậy, nó chắc chắn đã trở thành "sự thay thế xấu xí như thế nào"
moarboiler khắc

@moarboilerplate bạn tự nói điều đó, đi với giải pháp rẻ tiền và dễ dàng và nó sẽ biến thành một mớ hỗn độn. Đó là lý do tại sao các ngôn ngữ OO được phát minh, như là một giải pháp cho loại vấn đề này. con ngựa phân lớp ban đầu có vẻ đắt tiền, nhưng sớm trả tiền cho chính nó. Tiếp tục với các giải pháp đơn giản, nhưng lầy lội, chi phí ngày càng nhiều theo thời gian.
gbjbaanb

3

Vì câu hỏi có functional-programmingthẻ, chúng tôi có thể sử dụng loại tổng để phản ánh hai hương vị của ngựa và mẫu phù hợp để phân biệt giữa chúng. Chẳng hạn, trong F #:

type Equine =
| Horse
| Unicorn of hornLength: float

module equines =

  let averageHornLength (equines : Equine list) =
    equines 
    |> List.choose (fun x -> 
      match x with
      | Unicorn u -> Some(u)
      | _ -> None)
    |> List.average

let herd = [ Horse ; Horse ; Unicorn(35.0) ; Horse ; Unicorn(50.0) ]

printfn "Average horn length in herd : %f" (equines.averageHornLength herd) // prints 42.5

Trên OOP, FP có lợi thế phân tách dữ liệu / chức năng, điều này có thể giúp bạn thoát khỏi "lương tâm tội lỗi" (không chính đáng?) Khi vi phạm mức độ trừu tượng khi hạ thấp xuống các kiểu con cụ thể từ danh sách các đối tượng của siêu kiểu.

Ngược lại với các giải pháp OO được đề xuất trong các câu trả lời khác, khớp mẫu cũng cung cấp một điểm mở rộng dễ dàng hơn nếu một loài có sừng khác Equinexuất hiện vào một ngày nào đó.


2

Hình thức ngắn của cùng một câu trả lời ở cuối yêu cầu đọc sách hoặc bài viết trên web.

Mẫu khách

Vấn đề có một hỗn hợp của ngựa và kỳ lân. (Vi phạm nguyên tắc thay thế Liskov là một vấn đề phổ biến trong các cơ sở mã kế thừa.)

Thêm một phương thức cho ngựa và tất cả các lớp con

Horse.visit(EquineVisitor v)

Giao diện khách truy cập ngựa trông giống như thế này trong java / c #

interface EquineVisitor {
  void visitHorse(Horse z);
  void visitUnicorn(Unicorn z);
}

Unicorn.visit(EquineVisitor v){
   v.visitUnicorn(this);
}

Horse.visit(EquineVisitor v){
   v.visitHorse(this);
}

Để đo sừng bây giờ chúng ta viết ....

class HornMeasurer implements EquineVistor {
    void visitHorse(Horse h){} // ignore horses
    void visitUnicorn(Unicorn u){
         double len = u.getHornLength();
         totalLength+=len;
         unicornCount++;
    }

    double getAverageLength(){
          return totalLength/unicornCount;
    }

    double totalLength=0;
    int unicornCount=0;
}

Mô hình khách truy cập bị chỉ trích vì làm cho tái cấu trúc và tăng trưởng khó khăn hơn.

Trả lời ngắn: Sử dụng mẫu thiết kế Trình truy cập để nhận công văn kép.

xem thêm https://en.wikipedia.org/wiki/Visitor_potype

xem thêm http://c2.com/cgi/wiki?VisitorPotype để thảo luận về khách truy cập.

xem thêm Mẫu thiết kế của Gamma et al.


Tôi đã định trả lời với mẫu khách truy cập. Phải cuộn xuống một cách đáng ngạc nhiên để tìm nếu ai đó đã đề cập đến nó!
Ben Thurley

0

Giả sử rằng trong kiến ​​trúc của bạn kỳ lân là một phân loài ngựa và bạn bắt gặp những nơi bạn có một bộ sưu tập Horsenơi một số trong số chúng có thể là Unicorns, tôi sẽ đi theo phương pháp đầu tiên ( .OfType<Unicorn>()...) bởi vì đó là cách đơn giản nhất để thể hiện ý định của bạn . Đối với bất kỳ ai xuất hiện muộn hơn (bao gồm cả bản thân bạn trong 3 tháng), rõ ràng ngay lập tức những gì bạn đang cố gắng thực hiện với mã đó: chọn ra những con kỳ lân trong số những con ngựa.

Các phương pháp khác mà bạn liệt kê cảm thấy giống như một cách khác để đặt câu hỏi "Bạn có phải là một con kỳ lân không?". Ví dụ: nếu bạn sử dụng một số loại phương pháp đo sừng ngoại lệ, bạn có thể có mã trông giống như thế này:

foreach (var horse in horses)
{
    try
    {
        var length = horse.MeasureHorn();
        //...
    }
    catch (NoHornException e)
    {
        continue;
    }
}

Vì vậy, bây giờ ngoại lệ trở thành chỉ báo rằng một cái gì đó không phải là một con kỳ lân. Và bây giờ đây không thực sự là một tình huống đặc biệt nữa, mà là một phần của dòng chương trình bình thường. Và sử dụng một ngoại lệ thay vì ifdường như thậm chí còn bẩn hơn là chỉ thực hiện kiểm tra loại.

Hãy nói rằng bạn đi theo con đường giá trị ma thuật để kiểm tra sừng trên ngựa. Vì vậy, bây giờ các lớp học của bạn trông giống như thế này:

class Horse
{
    public double MeasureHorn() { return -1; }
    //...
}

class Unicorn : Horse
{
    public override double MeasureHorn { return _hornLength; }
    //...
}

Bây giờ Horselớp của bạn phải biết về Unicornlớp học và có các phương pháp bổ sung để đối phó với những điều nó không quan tâm. Bây giờ hãy tưởng tượng bạn cũng có Pegasuss và Zebras thừa hưởng từ Horse. Bây giờ Horsecần có một Flyphương pháp cũng như MeasureWings, CountStripesvv Và sau đó các Unicornlớp học được những phương pháp này quá. Bây giờ tất cả các lớp của bạn phải biết về nhau và bạn đã làm ô nhiễm các lớp với một loạt các phương thức không nên có chỉ để tránh hỏi hệ thống kiểu "Đây có phải là một con kỳ lân không?"

Vì vậy, những gì về việc thêm một cái gì đó để Horsenói nếu một cái gì đó là một Unicornvà xử lý tất cả các đo sừng? Chà, bây giờ bạn phải kiểm tra sự tồn tại của vật thể này để biết liệu có thứ gì đó là kỳ lân không (chỉ thay thế một kiểm tra cho một vật khác). Nó cũng làm vẩn đục nước một chút trong đó bây giờ bạn có thể có mộtList<Horse> unicornsđiều đó thực sự chứa tất cả các con kỳ lân, nhưng hệ thống loại và trình gỡ lỗi không thể dễ dàng cho bạn biết điều đó. "Nhưng tôi biết đó là tất cả những con kỳ lân" bạn nói, "cái tên thậm chí còn nói như vậy." Chà, nếu cái gì đó được đặt tên kém thì sao? Hoặc giả sử, bạn đã viết một cái gì đó với giả định rằng nó thực sự sẽ là tất cả các con kỳ lân, nhưng sau đó các yêu cầu đã thay đổi và bây giờ nó cũng có thể có pegasi trộn lẫn vào? (Bởi vì không có điều gì như vậy từng xảy ra, đặc biệt là trong phần mềm / châm biếm kế thừa.) Bây giờ hệ thống loại sẽ vui vẻ đưa pegasi của bạn vào với con kỳ lân của bạn. Nếu biến của bạn đã được khai báo là List<Unicorn>trình biên dịch (hoặc môi trường thời gian chạy) sẽ phù hợp nếu bạn cố gắng trộn trong pegasi hoặc ngựa.

Cuối cùng, tất cả các phương pháp này chỉ là sự thay thế cho việc kiểm tra hệ thống loại. Cá nhân, tôi không muốn phát minh lại bánh xe ở đây và hy vọng rằng mã của tôi hoạt động tốt như một cái gì đó được xây dựng và đã được thử nghiệm bởi hàng ngàn lập trình viên khác hàng ngàn lần.

Cuối cùng, mã cần phải dễ hiểu với bạn . Máy tính sẽ tìm ra nó bất kể bạn viết nó như thế nào. Bạn là người phải gỡ lỗi nó và có thể suy luận về nó. Hãy lựa chọn làm cho công việc của bạn dễ dàng hơn. Nếu vì một lý do nào đó, một trong những phương pháp khác này mang lại lợi thế cho bạn rằng vượt trội hơn mã rõ ràng hơn trong một vài điểm mà nó sẽ hiển thị, hãy tìm nó. Nhưng điều đó phụ thuộc vào cơ sở mã của bạn.


Ngoại lệ im lặng chắc chắn là xấu - đề xuất của tôi là một kiểm tra sẽ được if(horse.IsUnicorn) horse.MeasureHorn();và ngoại lệ sẽ không bị bắt - chúng sẽ được kích hoạt khi !horse.IsUnicornvà bạn đang ở trong một bối cảnh đo lường kỳ lân, hoặc bên trong MeasureHornmột con kỳ lân. Theo cách đó, khi ngoại lệ được ném, bạn không che dấu lỗi, nó hoàn toàn bị nổ tung và là một dấu hiệu cần phải sửa. Rõ ràng là nó chỉ phù hợp với một số trường hợp nhất định, nhưng đó là một triển khai không sử dụng ném ngoại lệ để xác định đường dẫn thực thi.
moarboiler khắc

0

Chà, có vẻ như miền ngữ nghĩa của bạn có mối quan hệ IS-A, nhưng bạn có một chút cảnh giác khi sử dụng các kiểu con / kế thừa để mô hình hóa đặc biệt này vì phản xạ kiểu thời gian chạy. Tuy nhiên, tôi nghĩ rằng bạn sợ điều sai trái của việc phân nhóm thực sự đi kèm với những nguy hiểm, nhưng thực tế là bạn đang truy vấn một đối tượng trong thời gian chạy không phải là vấn đề. Bạn sẽ thấy những gì tôi muốn nói.

Lập trình hướng đối tượng đã dựa khá nhiều vào khái niệm mối quan hệ IS-A, nó được cho là dựa quá nhiều vào nó, dẫn đến hai khái niệm quan trọng nổi tiếng:

Nhưng tôi nghĩ có một cách khác, dựa trên lập trình chức năng hơn để xem xét các mối quan hệ IS-A mà có lẽ không có những khó khăn này. Đầu tiên, chúng tôi muốn mô hình ngựa và kỳ lân trong chương trình của chúng tôi, vì vậy chúng tôi sẽ có một Horsevà một Unicornloại. Các giá trị của các loại này là gì? Vâng, tôi sẽ nói điều này:

  1. Các giá trị của các loại này là đại diện hoặc mô tả về ngựa và kỳ lân (tương ứng);
  2. Chúng là các biểu diễn hoặc mô tả được lược đồ hóa Chúng không phải là dạng tự do, chúng được xây dựng theo các quy tắc rất nghiêm ngặt.

Điều đó nghe có vẻ hiển nhiên, nhưng tôi nghĩ một trong những cách mọi người gặp phải các vấn đề như vấn đề hình elip hình tròn là bằng cách không quan tâm đến những điểm đó một cách cẩn thận. Mỗi vòng tròn là một hình elip, nhưng điều đó không có nghĩa là mọi mô tả được lược đồ hóa của một vòng tròn sẽ tự động là một mô tả được lược đồ hóa của một hình elip theo một lược đồ khác nhau. Nói cách khác, chỉ vì hình tròn là hình elip không có nghĩa là a Circlelà một Ellipse, có thể nói như vậy. Nhưng nó có nghĩa là:

  1. Có một hàm tổng số chuyển đổi bất kỳ Circle(mô tả vòng tròn được lược đồ) thành một Ellipse(loại mô tả khác nhau) mô tả cùng một vòng tròn;
  2. Có một hàm một phần lấy một Ellipsevà, nếu mô tả một vòng tròn, trả về tương ứng Circle.

Vì vậy, trong các thuật ngữ lập trình chức năng, Unicornkiểu của bạn không cần phải là một kiểu con của Horsetất cả, bạn chỉ cần các thao tác như sau:

-- Convert any unicorn-description of into a horse-description that
-- describes the same unicorns.
toHorse :: Unicorn -> Horse

-- If the horse described by the given horse-description is a unicorn,
-- then return a unicorn-description of that unicorn, otherwise return
-- nothing.
toUnicorn :: Horse -> Maybe Unicorn

toUnicorncần phải là một nghịch đảo đúng của toHorse:

toUnicorn (toHorse x) = Just x

MaybeLoại của Haskell là loại mà các ngôn ngữ khác gọi là loại "tùy chọn". Ví dụ, loại Java 8 Optional<Unicorn>là một Unicornhoặc không có gì. Lưu ý rằng hai trong số các lựa chọn thay thế của bạn, ném ra một ngoại lệ hoặc trả về một "giá trị ma thuật hoặc mặc định" rất giống với các loại tùy chọn.

Vì vậy, về cơ bản những gì tôi đã làm ở đây là xây dựng lại khái niệm mối quan hệ IS-A về các loại và chức năng, mà không sử dụng các kiểu con hoặc kế thừa. Những gì tôi sẽ lấy đi từ đây là:

  1. Mô hình của bạn cần phải có một Horseloại;
  2. Các Horsenhu cầu loại để mã hóa đầy đủ thông tin để xác định một cách rõ ràng cho dù bất kỳ giá trị mô tả một con kỳ lân;
  3. Một số hoạt động của Horseloại cần phải phơi bày thông tin đó để khách hàng của loại có thể quan sát xem một cái đã cho Horsecó phải là một con kỳ lân hay không;
  4. Các khách hàng của Horseloại sẽ phải sử dụng các hoạt động sau này trong thời gian chạy để phân biệt giữa kỳ lân và ngựa.

Vì vậy, đây về cơ bản Horselà một mô hình "hỏi mọi người xem nó có phải là một con kỳ lân" không. Bạn cảnh giác với mô hình đó, nhưng tôi nghĩ sai như vậy. Nếu tôi đưa cho bạn một danh sách Horses, tất cả những gì đảm bảo loại đó là những thứ mà các mục trong danh sách mô tả là ngựa, vì vậy, chắc chắn bạn sẽ cần phải làm gì đó trong thời gian chạy để cho biết chúng là kỳ lân. Vì vậy, không có vấn đề gì, tôi nghĩ rằng bạn cần phải thực hiện các hoạt động sẽ làm điều đó cho bạn.

Trong lập trình hướng đối tượng, cách làm quen thuộc này là như sau:

  • Có một Horseloại;
  • Unicornmột kiểu con của Horse;
  • Sử dụng sự phản chiếu kiểu thời gian chạy như là hoạt động có thể truy cập của máy khách để phân biệt xem một cái đã cho Horsecó phải là một Unicorn.

Điều này có một điểm yếu lớn, khi bạn nhìn nó từ góc độ "điều so với mô tả" mà tôi đã trình bày ở trên:

  • Điều gì xảy ra nếu bạn có một Horseví dụ mô tả một con kỳ lân nhưng không phải là một Unicornthể hiện?

Quay trở lại từ đầu, đây là điều tôi nghĩ là phần thực sự đáng sợ khi sử dụng phân nhóm và truyền phát để mô hình hóa mối quan hệ IS-A này không phải là việc bạn phải thực hiện kiểm tra thời gian chạy. Lạm dụng kiểu chữ một chút, hỏi Horsexem đó có phải là một Unicorntrường hợp không đồng nghĩa với việc hỏi Horseliệu đó có phải là một con kỳ lân không (cho dù đó là một Horsemô tả của một con ngựa cũng là một con kỳ lân). Không, trừ khi chương trình của bạn đã đi rất lâu để đóng gói mã xây dựng Horsesđể mỗi khi khách hàng cố gắng xây dựng một Horsemô tả một con kỳ lân, Unicornlớp sẽ được khởi tạo. Theo kinh nghiệm của tôi, hiếm khi các lập trình viên làm việc này một cách cẩn thận.

Vì vậy, tôi sẽ đi theo cách tiếp cận trong đó có một hoạt động rõ ràng, không bị hạ thấp chuyển đổi Horses thành Unicorns. Đây có thể là một phương thức của Horseloại:

interface Horse {
    // ...
    Optional<Unicorn> toUnicorn();
}

... Hoặc nó có thể là một vật thể bên ngoài ("vật thể riêng biệt của bạn trên một con ngựa cho bạn biết con ngựa đó có phải là một con kỳ lân hay không"):

class HorseToUnicornCoercion {
    Optional<Unicorn> convert(Horse horse) {
       // ...
    }
}

Sự lựa chọn giữa các vấn đề này là cách thức tổ chức chương trình của bạn trong cả hai trường hợp, bạn có Horse -> Maybe Unicornhoạt động tương đương với hoạt động của tôi từ trên xuống, bạn chỉ đóng gói nó theo những cách khác nhau (điều đó sẽ thừa nhận có tác động gợn lên những hoạt động mà Horseloại cần để tiếp xúc với khách hàng của mình).


-1

Nhận xét của OP trong một câu trả lời khác làm rõ câu hỏi, tôi nghĩ

đó cũng là một phần của những gì câu hỏi đang hỏi Nếu tôi có một đàn ngựa, và một vài trong số chúng là kỳ lân về mặt khái niệm, làm thế nào chúng tồn tại để vấn đề có thể được giải quyết một cách sạch sẽ mà không có quá nhiều tác động tiêu cực?

Phrased theo cách đó, tôi nghĩ rằng chúng ta cần thêm thông tin. Câu trả lời có thể phụ thuộc vào một số điều:

  • Cơ sở ngôn ngữ của chúng tôi. Ví dụ, tôi có thể tiếp cận điều này khác nhau trong ruby ​​và javascript và Java.
  • Các khái niệm bản thân: Điều gì một con ngựa và những gì một con kỳ lân? Dữ liệu nào được liên kết với nhau? Chúng giống hệt nhau ngoại trừ sừng, hay chúng có những khác biệt khác?
  • Làm thế nào khác chúng ta đang sử dụng chúng, ngoài việc lấy trung bình chiều dài sừng? Còn đàn thì sao? Có lẽ chúng ta cũng nên mô hình hóa chúng? Chúng ta có sử dụng chúng ở nơi khác không? herd.averageHornLength()dường như phù hợp với mô hình khái niệm của chúng tôi.
  • Làm thế nào là con ngựa và kỳ lân được tạo ra? Là thay đổi mã đó trong giới hạn tái cấu trúc của chúng tôi?

Tuy nhiên, nói chung, tôi thậm chí sẽ không nghĩ về thừa kế và các kiểu con ở đây. Bạn có một danh sách các đối tượng. Một số đối tượng có thể được xác định là kỳ lân, có lẽ vì chúng có một hornLength()phương pháp. Lọc danh sách dựa trên thuộc tính độc nhất kỳ lân này. Bây giờ vấn đề đã được giảm xuống trung bình chiều dài sừng của một danh sách các con kỳ lân.

OP, hãy cho tôi biết nếu tôi vẫn hiểu lầm ...


1
Điểm công bằng. Để giữ cho vấn đề không trở nên trừu tượng hơn, chúng ta phải đưa ra một số giả định hợp lý: 1) ngôn ngữ được gõ mạnh 2) đàn có thể hạn chế ngựa thành một loại, có lẽ do một bộ sưu tập 3) kỹ thuật như gõ vịt có lẽ nên tránh . Đối với những gì có thể thay đổi, không nhất thiết có bất kỳ giới hạn nào, nhưng mỗi loại thay đổi có hậu quả riêng của nó ...
moarboilerplate

Nếu đàn ràng buộc ngựa thành một loại, không phải là sự lựa chọn duy nhất của chúng tôi (không thích tùy chọn đó) hoặc đối tượng bao bọc (giả sử HerdMember) mà chúng tôi khởi tạo với một con ngựa hoặc kỳ lân (ngựa tự do và kỳ lân không cần mối quan hệ phụ ). HerdMembersau đó được tự do thực hiện isUnicorn()tuy nhiên nó thấy phù hợp và giải pháp lọc mà tôi đề xuất theo sau.
Giô-na

Trong một số ngôn ngữ, hornLpm () có thể được trộn lẫn và nếu đó là trường hợp, nó có thể là một giải pháp hợp lệ. Tuy nhiên, trong các ngôn ngữ mà việc gõ kém linh hoạt, bạn phải sử dụng một số kỹ thuật hackish để làm điều tương tự hoặc bạn phải làm một cái gì đó như đặt hornlength lên một con ngựa, nơi nó có thể dẫn đến sự nhầm lẫn trong mã bởi vì con ngựa không ' T khái niệm có bất kỳ sừng. Ngoài ra, nếu thực hiện các tính toán toán học, bao gồm các giá trị mặc định có thể làm lệch kết quả (xem các bình luận dưới câu hỏi ban đầu)
moarboilerplate

Mặc dù vậy, Mixins trừ khi chúng được thực hiện là thời gian chạy, chỉ là sự kế thừa dưới tên khác. Nhận xét của bạn "một con ngựa không có sừng về mặt khái niệm" liên quan đến nhận xét của tôi về việc cần biết thêm về chúng là gì, nếu câu trả lời của chúng tôi cần bao gồm cách chúng tôi mô hình ngựa và kỳ lân và mối quan hệ của chúng với nhau là gì. Bất kỳ giải pháp nào bao gồm các giá trị mặc định đều nằm ngoài tầm imo.
Giô-na

Bạn đúng ở chỗ để có được một giải pháp chính xác cho một biểu hiện cụ thể của vấn đề này, bạn cần phải có nhiều bối cảnh. Để trả lời câu hỏi của bạn về một con ngựa có sừng và buộc nó trở lại với mixins, tôi đã nghĩ đến một kịch bản trong đó một cái sừng bước vào một con ngựa không phải là một con kỳ lân là một lỗi. Xem xét một đặc điểm Scala có triển khai mặc định cho hornLpm ném ngoại lệ. Một loại kỳ lân có thể ghi đè lên việc thực hiện đó và nếu một con ngựa từng đưa nó vào một bối cảnh nơi đánh giá độ dài sừng thì đó là một ngoại lệ.
moarboiler khắc

-2

Một phương thức GetUnicorns () trả về IEnumerable dường như là giải pháp thanh lịch, linh hoạt và phổ biến nhất đối với tôi. Bằng cách này, bạn có thể đối phó với bất kỳ (kết hợp) đặc điểm nào để xác định xem một con ngựa sẽ vượt qua như một con kỳ lân, không chỉ là loại lớp hoặc giá trị của một tài sản cụ thể.


Tôi đồng ý với điều này. Mason Wheeler cũng có một giải pháp tốt trong câu trả lời của mình, nhưng nếu bạn cần tìm ra một con kỳ lân vì nhiều lý do khác nhau ở những nơi khác nhau, mã của bạn sẽ có rất nhiều horses.ofType<Unicorn>...cấu trúc. Có một GetUnicornschức năng sẽ là một lớp lót, nhưng nó sẽ có khả năng chống lại những thay đổi đối với mối quan hệ ngựa / kỳ lân theo quan điểm của người gọi.
Shaz

@Ryan Nếu bạn trả lại một IEnumerable<Horse>, mặc dù tiêu chí kỳ lân của bạn ở một nơi, nó được gói gọn, vì vậy người gọi của bạn phải đưa ra giả định về lý do tại sao họ cần kỳ lân (tôi có thể nhận được món ngao bằng cách gọi món súp trong ngày hôm nay, nhưng điều đó không ' t có nghĩa là tôi sẽ có được nó vào ngày mai bằng cách làm điều tương tự). Thêm vào đó, bạn phải để lộ giá trị mặc định cho còi trên Horse. Nếu Unicornlà loại riêng của nó, bạn phải tạo một loại mới và duy trì ánh xạ loại, có thể giới thiệu chi phí chung.
moarboiler khắc

1
@moarboilerplate: Chúng tôi xem xét tất cả những điều đó hỗ trợ cho giải pháp. Phần làm đẹp là nó độc lập với bất kỳ chi tiết thực hiện nào của kỳ lân. Cho dù bạn phân biệt đối xử dựa trên thành viên dữ liệu, lớp hoặc thời gian trong ngày (những con ngựa đó đều có thể biến thành kỳ lân vào nửa đêm nếu mặt trăng phù hợp với tất cả những gì tôi biết), giải pháp vẫn giữ nguyên, giao diện vẫn như cũ.
Martin Maat
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.