Thiết kế các mẫu để tránh [đóng]


105

Nhiều người dường như đồng ý rằng mô hình Singleton có một số nhược điểm và một số người thậm chí còn đề nghị tránh hoàn toàn mô hình này. Có một cuộc thảo luận tuyệt vời ở đây . Vui lòng hướng bất kỳ nhận xét nào về mẫu Singleton đến câu hỏi đó.

Câu hỏi của tôi : Có những mẫu thiết kế nào khác, cần tránh hoặc sử dụng hết sức cẩn thận không?


Tôi chỉ phải thông báo về danh sách lớn của thiết kế Antipatterns deviq.com/antipatterns
Nhà phát triển

@casperOne tại sao bạn đóng câu hỏi? Câu hỏi là chính đáng.
bleepzter

Câu trả lời:


149

Các mẫu phức tạp

Tất cả các mẫu thiết kế nên được sử dụng cẩn thận. Theo tôi, bạn nên cấu trúc lại các mẫu khi có lý do hợp lệ để làm như vậy thay vì triển khai một mẫu ngay lập tức. Vấn đề chung với việc sử dụng các mẫu là chúng thêm phức tạp. Việc lạm dụng các mẫu làm cho một ứng dụng hoặc hệ thống nhất định trở nên cồng kềnh để phát triển và duy trì thêm.

Hầu hết thời gian, có một giải pháp đơn giản và bạn sẽ không cần áp dụng bất kỳ mẫu cụ thể nào. Một nguyên tắc chung là sử dụng một mẫu bất cứ khi nào các đoạn mã có xu hướng được thay thế hoặc cần được thay đổi thường xuyên và chuẩn bị sẵn sàng để đề phòng mã phức tạp khi sử dụng một mẫu.

Hãy nhớ rằng mục tiêu của bạn phải là sự đơn giản và sử dụng một khuôn mẫu nếu bạn thấy nhu cầu thiết thực để hỗ trợ thay đổi trong mã của mình.

Nguyên tắc về các mẫu

Nó có vẻ giống như một cuộc tranh luận để sử dụng các mẫu nếu chúng rõ ràng có thể dẫn đến các giải pháp được thiết kế quá mức và phức tạp. Tuy nhiên, thay vào đó, việc đọc các kỹ thuật và nguyên tắc thiết kế đặt nền tảng cho hầu hết các mẫu sẽ thú vị hơn nhiều đối với một lập trình viên . Trên thực tế, một trong những cuốn sách yêu thích của tôi về 'các mẫu thiết kế' nhấn mạnh điều này bằng cách nhắc lại những nguyên tắc nào có thể áp dụng cho mẫu được đề cập. Chúng đủ đơn giản để hữu ích hơn các mẫu về mức độ liên quan. Một số nguyên tắc đủ chung để bao hàm nhiều hơn lập trình hướng đối tượng (OOP), chẳng hạn như Nguyên tắc thay thế Liskov , miễn là bạn có thể xây dựng các mô-đun mã của mình.

Có vô số nguyên tắc thiết kế nhưng những nguyên tắc được mô tả trong chương đầu tiên của cuốn sách GoF khá hữu ích để bắt đầu.

  • Chương trình thành một 'giao diện', không phải là 'triển khai'. (Gang of Four 1995: 18)
  • Ủng hộ 'thành phần đối tượng' hơn 'kế thừa lớp'. (Gang of Four 1995: 20)

Hãy để những điều đó ngấm vào bạn một lúc. Cần lưu ý rằng khi GoF được viết, một giao diện có nghĩa là bất cứ thứ gì trừu tượng (cũng có nghĩa là siêu lớp), không nên nhầm lẫn với giao diện là một kiểu trong Java hoặc C #. Nguyên tắc thứ hai đến từ việc lạm dụng thừa kế được quan sát thấy vẫn còn phổ biến cho đến ngày nay .

Từ đó, bạn có thể đọc các nguyên tắc SOLID được Robert Cecil Martin (hay còn gọi là Uncle Bob) đưa ra . Scott Hanselman đã phỏng vấn chú Bob trong một podcast về những nguyên tắc sau:

  • Nguyên tắc trách nhiệm của S ingle
  • Nguyên tắc đóng của O pen
  • Nguyên tắc thay thế L iskov
  • I Nguyên tắc phân tách giao diện
  • Nguyên tắc nghịch đảo phụ thuộc D

Những nguyên tắc này là một khởi đầu tốt để đọc và thảo luận với đồng nghiệp của bạn. Bạn có thể thấy rằng các nguyên tắc đan xen lẫn nhau và với các quy trình khác như tách các mối quan tâmtiêm nhiễm phụ thuộc . Sau khi thực hiện TDD một thời gian, bạn cũng có thể thấy rằng những nguyên tắc này xuất hiện một cách tự nhiên trong thực tế vì bạn cần tuân theo chúng ở một mức độ nào đó để tạo ra các bài kiểm tra đơn vị có thể lặp lạicô lập .


7
+1 Câu trả lời rất hay. Có vẻ như mọi lập trình viên (newbie) ngày nay đều biết các mẫu thiết kế của mình hoặc ít nhất là biết chúng tồn tại. Nhưng rất nhiều người chưa bao giờ nghe nói đến, chứ đừng nói đến việc áp dụng, một số nguyên tắc hoàn toàn cần thiết như Trách nhiệm duy nhất để quản lý độ phức tạp của mã của họ.
eljenso

21

Bản thân các tác giả của Design Patterns cũng lo lắng nhất là mẫu "Khách".

Đó là một "điều xấu cần thiết" - nhưng thường được sử dụng quá mức và nhu cầu về nó thường bộc lộ một lỗ hổng cơ bản hơn trong thiết kế của bạn.

Tên thay thế cho mẫu "Khách truy cập" là "Nhiều cử", bởi vì mẫu Khách truy cập là thứ mà bạn kết thúc khi bạn muốn sử dụng ngôn ngữ OO của phái cử một loại để chọn mã để sử dụng dựa trên loại hai (hoặc nhiều hơn) các đối tượng khác nhau.

Ví dụ cổ điển là bạn có giao điểm giữa hai hình dạng, nhưng có một trường hợp đơn giản hơn thường bị bỏ qua: so sánh sự bằng nhau của hai đối tượng không đồng nhất.

Dù sao, bạn thường kết thúc với một cái gì đó như thế này:

interface IShape
{
    double intersectWith(Triangle t);
    double intersectWith(Rectangle r);
    double intersectWith(Circle c);
}

Vấn đề với điều này là bạn đã kết hợp tất cả các triển khai "IShape" của mình lại với nhau. Bạn đã ngụ ý rằng bất cứ khi nào bạn muốn thêm một hình dạng mới vào hệ thống phân cấp, bạn cũng sẽ cần phải thay đổi tất cả các triển khai "Hình dạng" khác.

Đôi khi, đây là thiết kế tối giản chính xác - nhưng hãy suy nghĩ kỹ. Thiết kế của bạn có thực sự bắt buộc bạn phải gửi đến hai loại không? Bạn có sẵn sàng viết từng sự bùng nổ tổ hợp của nhiều phương pháp không?

Thông thường, bằng cách đưa ra một khái niệm khác, bạn có thể giảm số lượng kết hợp mà bạn thực sự sẽ phải viết:

interface IShape
{
    Area getArea();
}

class Area
{
    public double intersectWith(Area otherArea);
    ...
}

Tất nhiên, nó phụ thuộc - đôi khi bạn thực sự cần phải viết mã để xử lý tất cả các trường hợp khác nhau - nhưng bạn nên tạm dừng và suy nghĩ trước khi bắt tay vào sử dụng Visitor. Nó có thể giúp bạn đỡ đau đớn sau này.


2
Phát biểu về khách truy cập, Bác Bob sử dụng nó "mọi thời đại là" butunclebob.com/ArticleS.UncleBob.IuseVisitor
Spoike

3
@Paul Hollingsworth Bạn có thể cung cấp tài liệu tham khảo về nơi mà nó nói rằng các tác giả của Mẫu thiết kế đang lo lắng (và tại sao họ lại lo lắng) không?
m3th0dman

16

Singletons - một lớp sử dụng singleton X có sự phụ thuộc vào nó, khó thấy và khó phân lập để thử nghiệm.

Chúng được sử dụng rất thường xuyên vì chúng thuận tiện và dễ hiểu, nhưng chúng thực sự có thể làm phức tạp việc kiểm tra.

Hãy xem Singletons là Kẻ dối trá bệnh lý .


1
Họ cũng có thể đơn giản hóa thử nghiệm vì họ có thể cung cấp cho bạn một điểm duy nhất để đưa vào một đối tượng Mock. Đó là tất cả để có được sự cân bằng đúng.
Martin Brown

1
@Martin: Nếu tất nhiên có thể thay đổi một singelton để thử nghiệm (nếu bạn không sử dụng triển khai singelton tiêu chuẩn), nhưng làm thế nào điều đó dễ dàng hơn việc vượt qua triển khai thử nghiệm trong hàm tạo?
orip

14

Tôi tin rằng mẫu Phương pháp Mẫu nói chung là một mẫu rất nguy hiểm.

  • Rất nhiều lần nó sử dụng hết hệ thống phân cấp kế thừa của bạn vì "những lý do sai lầm".
  • Các lớp cơ sở có xu hướng trở nên rải rác với tất cả các loại mã không liên quan.
  • Nó buộc bạn phải khóa thiết kế, thường là khá sớm trong quá trình phát triển. (Khóa sớm trong nhiều trường hợp)
  • Thay đổi điều này ở giai đoạn sau trở nên khó hơn và khó hơn.

2
Tôi sẽ nói thêm rằng bất cứ khi nào bạn sử dụng Phương pháp Mẫu, bạn có thể sử dụng Chiến lược tốt hơn. Vấn đề với TemplateMethod là có sự gần lại giữa lớp cơ sở và lớp dẫn xuất, thường được ghép nối quá mức.
Paul Hollingsworth

5
@Paul: Phương pháp mẫu rất tuyệt vời khi được sử dụng đúng cách, tức là khi các phần thay đổi cần biết nhiều về những phần không có. Ý kiến ​​của tôi là chiến lược nên được sử dụng khi mã cơ sở chỉ gọi mã tùy chỉnh và phương pháp mẫu nên được sử dụng khi mã tùy chỉnh vốn dĩ cần biết về mã cơ sở.
dsimcha

vâng dsimcha, tôi đồng ý ... miễn là người thiết kế lớp nhận thức được điều này.
Paul Hollingsworth

9

Tôi không nghĩ rằng bạn nên tránh các Mẫu thiết kế (DP), và tôi không nghĩ rằng bạn nên ép mình sử dụng DP khi lập kế hoạch kiến ​​trúc của mình. Chúng ta chỉ nên sử dụng DP khi chúng tự nhiên xuất hiện trong kế hoạch của chúng ta.

Nếu chúng tôi xác định ngay từ đầu rằng chúng tôi muốn sử dụng một DP nhất định, thì nhiều quyết định thiết kế trong tương lai của chúng tôi sẽ bị ảnh hưởng bởi sự lựa chọn đó, không có gì đảm bảo rằng DP mà chúng tôi chọn là phù hợp với nhu cầu của chúng tôi.

Một điều chúng ta cũng không nên làm là coi DP như một thực thể bất biến, chúng ta nên điều chỉnh mô hình cho phù hợp với nhu cầu của mình.

Vì vậy, tóm lại, tôi không nghĩ chúng ta nên tránh các DP, chúng ta nên nắm lấy chúng khi chúng đã thành hình trong kiến ​​trúc của chúng ta.


7

Tôi nghĩ rằng Active Record là một mẫu được sử dụng quá mức khuyến khích trộn logic nghiệp vụ với mã tồn tại. Nó không thực hiện tốt công việc ẩn việc triển khai lưu trữ khỏi lớp mô hình và liên kết các mô hình với cơ sở dữ liệu. Có rất nhiều lựa chọn thay thế (được mô tả trong PoEAA) như Cổng dữ liệu bảng, Cổng dữ liệu hàng và Người lập bản đồ dữ liệu thường cung cấp giải pháp tốt hơn và chắc chắn giúp cung cấp tính trừu tượng tốt hơn cho việc lưu trữ. Ngoài ra, mô hình của bạn không cần phải được lưu trữ trong cơ sở dữ liệu; còn việc lưu trữ chúng dưới dạng XML hoặc truy cập chúng bằng các dịch vụ web thì sao? Làm thế nào dễ dàng thay đổi cơ chế lưu trữ của các mô hình của bạn?

Điều đó nói rằng, Active Record không phải lúc nào cũng xấu và hoàn hảo cho các ứng dụng đơn giản hơn, nơi các tùy chọn khác sẽ quá mức cần thiết.


1
Kinda đúng, nhưng phụ thuộc ở một mức độ nào đó vào việc thực hiện.
Mike Woodhouse

6

Đơn giản là ... tránh các Mẫu thiết kế không rõ ràng đối với bạn hoặc những mẫu mà bạn không cảm thấy thoải mái .

Để kể tên một số ...

có một số mẫu không thực tế , chẳng hạn như:

  • Interpreter
  • Flyweight

cũng có một số khó nắm bắt hơn , chẳng hạn như:

  • Abstract Factory - Mô hình nhà máy trừu tượng đầy đủ với các gia đình của các đối tượng được tạo ra không phải là một điều dễ dàng như nó có vẻ
  • Bridge - Có thể quá trừu tượng, nếu trừu tượng và thực hiện được chia thành cây con, nhưng là mẫu rất hữu ích trong một số trường hợp
  • Visitor - Sự hiểu biết về cơ chế điều phối kép thực sự là PHẢI

và có một số mẫu trông rất đơn giản , nhưng không phải là lựa chọn rõ ràng vì nhiều lý do khác nhau liên quan đến nguyên tắc hoặc cách thực hiện của chúng:

  • Singleton - không hẳn là mẫu hoàn toàn xấu, chỉ là QUÁ bị lạm dụng (thường ở đó, nơi không phù hợp)
  • Observer - mẫu tuyệt vời ... chỉ làm cho mã khó đọc và gỡ lỗi hơn nhiều
  • Prototype - kiểm tra trình biên dịch giao dịch cho tính năng động (có thể tốt hoặc xấu ... tùy thuộc)
  • Chain of responsibility - quá thường xuyên bị ép buộc / giả tạo vào thiết kế

Đối với những "cái không thực tế", người ta thực sự nên suy nghĩ trước khi sử dụng chúng, bởi vì thường có giải pháp thanh lịch hơn ở đâu đó.

Đối với những người "khó nắm bắt" hơn ... chúng thực sự là sự trợ giúp tuyệt vời, khi chúng được sử dụng ở những nơi phù hợp và khi chúng được thực hiện tốt ... nhưng chúng lại là cơn ác mộng, khi được sử dụng không đúng cách.

Bây giờ, điều gì tiếp theo ...


Mẫu Flyweight là điều bắt buộc phải có bất cứ lúc nào khi bạn sử dụng một tài nguyên, thường là một hình ảnh, nhiều hơn một lần. Nó không phải là một khuôn mẫu, nó là một giải pháp.
Cengiz Kandemir

5

Tôi hy vọng tôi sẽ không bị đánh đập quá nhiều vì điều này. Christer Ericsson đã viết hai bài báo ( một , hai ) về chủ đề các mẫu thiết kế trong blog phát hiện va chạm thời gian thực của mình . Giọng điệu của anh ta khá gay gắt, và có lẽ hơi khiêu khích, nhưng người đàn ông biết những thứ của anh ta, vì vậy tôi sẽ không coi đó là lời nói tục tĩu của một kẻ mất trí.


Những lần đọc thú vị. Cảm ơn vì các liên kết!
Bombe

3
Những kẻ ngu ngốc tạo ra mã xấu. Có phải những kẻ ngu ngốc có hoa văn tạo ra mã tệ hơn những kẻ ngu ngốc chưa bao giờ nhìn thấy hoa văn không? Tôi không nghĩ là họ làm. Đối với những người thông minh, các mẫu cung cấp vốn từ vựng nổi tiếng, giúp giảm bớt việc trao đổi ý tưởng. Giải pháp: Tìm hiểu các mẫu và chỉ đối phó với các lập trình viên thông minh.
Martin Brown

Tôi không nghĩ rằng thật sự có thể cho một kẻ ngu ngốc thực sự tạo ra mã tệ hơn - bất kể họ đang sử dụng công cụ nào
1800 INFORMATION

1
Tôi nghĩ rằng ví dụ của anh ấy với bài kiểm tra đại học của anh ấy chỉ chứng minh rằng những người khinh thường lĩnh vực vấn đề của họ và không muốn nghiên cứu nó trong hơn vài giờ trong một ngày cuối tuần sẽ đưa ra câu trả lời sai khi cố gắng giải quyết vấn đề.
scriptocalypse

5

Một số người nói rằng bộ định vị dịch vụ là một mô hình chống lại.


Cũng cần lưu ý rằng đôi khi bộ định vị dịch vụ là cần thiết. Ví dụ: khi bạn không có quyền kiểm soát thích hợp việc khởi tạo đối tượng (ví dụ: các thuộc tính có tham số không phải là hằng số trong C #). Nhưng cũng có thể sử dụng bộ định vị dịch vụ VỚI tiêm ctor.
Sinaesthetic

2

Tôi tin rằng mô hình quan sát viên có rất nhiều câu trả lời, nó hoạt động trong những trường hợp rất chung chung, nhưng khi các hệ thống trở nên phức tạp hơn, nó trở thành cơn ác mộng, cần thông báo OnBefore (), OnAfter () và thường đăng các tác vụ không đồng bộ để tránh tái sự say mê. Một giải pháp tốt hơn nhiều là phát triển một hệ thống phân tích phụ thuộc tự động, công cụ tất cả các đối tượng truy cập (với các rào cản đọc) trong quá trình tính toán và tự động tạo ra một cạnh trong biểu đồ phụ thuộc.


4
Tôi đã hiểu tất cả mọi thứ trong câu trả lời của bạn cho đến khi từ "A"
1800 THÔNG TIN

Bạn có thể cần mở rộng hoặc liên kết đến phân tích phụ thuộc tự động mà bạn nói đến này. Ngoài ra trong .NET các đại biểu / sự kiện được sử dụng thay vì mẫu quan sát.
Spoike

3
@Spoike: đại biểu / sự kiện là một thực hiện các mô hình quan sát
orip

1
Mối hận thù cá nhân của tôi đối với Observer là nó có thể tạo ra các rò rỉ bộ nhớ trong các ngôn ngữ được thu thập rác. Khi bạn hoàn thành một đối tượng, bạn cần nhớ rằng đối tượng sẽ không được dọn dẹp.
Martin Brown

@orip: vâng, đó là lý do tại sao bạn sử dụng đại biểu / sự kiện. ;)
Spoike

2

Phần bổ sung cho bài đăng của Spoike, Tái cấu trúc lại các mẫu là một bài đọc hay.


Tôi thực sự đã liên kết với danh mục sách trên Internet. :)
Spoike

Oh! Tôi không thèm di chuột nó. Thực ra, ngay sau khi kết thúc câu hỏi, cuốn sách này đã xuất hiện trong đầu tôi, và sau đó tôi thấy câu trả lời của bạn. Tôi không thể ngăn mình đăng nó. :)
Adeel Ansari

0

Lặp lại là một trong những mẫu GoF cần tránh, hoặc ít nhất là chỉ sử dụng nó khi không có lựa chọn thay thế nào.

Các lựa chọn thay thế là:

  1. cho mỗi vòng lặp. Cấu trúc này hiện diện trong hầu hết các ngôn ngữ chính thống và có thể được sử dụng để tránh trình lặp trong đa số trường hợp.

  2. bộ chọn là LINQ hoặc jQuery. Chúng nên được sử dụng khi for-each là không thích hợp vì không phải tất cả các đối tượng từ thùng chứa đều phải được xử lý. Không giống như trình vòng lặp, bộ chọn cho phép hiển thị ở một nơi loại đối tượng sẽ được xử lý.


Tôi đồng ý với các bộ chọn. Foreach là một trình lặp, hầu hết các ngôn ngữ OO cung cấp một giao diện có thể lặp lại mà bạn triển khai để cho phép foreach.
Neil Aitken

Trong một số ngôn ngữ, mỗi cấu trúc có thể được thực hiện thông qua trình vòng lặp, nhưng khái niệm về nó thực sự ở cấp độ cao hơn và gần với bộ chọn hơn. Khi sử dụng for-each nhà phát triển tuyên bố rõ ràng rằng tất cả các phần tử từ vùng chứa phải được xử lý.
Volodymyr Frolov

Trình lặp là một mẫu tuyệt vời. Anti-pattern sẽ triển khai IEnumerable / IEnumerator mà không có trình lặp. Tôi tin rằng LINQ đã có thể thực hiện được thông qua yieldtrình lặp. Eric White có một số cuộc thảo luận tuyệt vời về điều này trong C # 3.0: blog.msdn.com/b/ericwhite/archive/2006/10/04/… . Ngoài ra, hãy xem cuộc thảo luận của Jeremy Likness về các quy trình đăng ký với trình vòng lặp: wintellect.com/CS/blogs/jlikness/archive/2010/03/23/… .

@Ryan Riley, các trình vòng lặp là các đối tượng cấp thấp và do đó chúng phải được tránh trong thiết kế và mã cấp cao. Chi tiết về việc triển khai các trình vòng lặp và các loại bộ chọn khác nhau không quan trọng ở đây. Các bộ chọn, không giống như Bộ lặp cho phép lập trình viên thể hiện rõ ràng những gì họ muốn xử lý và do đó chúng ở mức cao.
Volodymyr Frolov

Fwiw, cú pháp thay thế, giống như LINQ F # là `List.map (fun x -> x.Value) xs`, chỉ chừng đó bạn có thể hiểu được danh sách.
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.