Vật đúc
Điều này gần như chắc chắn sẽ là một tiếp tuyến hoàn chỉnh cho cách tiếp cận của cuốn sách được trích dẫn, nhưng một cách để phù hợp hơn với ISP là nắm lấy một tư duy đúc tại một khu vực trung tâm của cơ sở mã của bạn bằng cách sử dụng QueryInterface
phương pháp COM.
Rất nhiều cám dỗ để thiết kế các giao diện chồng chéo trong bối cảnh giao diện thuần túy thường xuất phát từ mong muốn làm cho các giao diện "tự túc" hơn là thực hiện một trách nhiệm chính xác, giống như bắn tỉa.
Ví dụ, có vẻ kỳ lạ khi thiết kế các chức năng máy khách như thế này:
// Returns the absolute position of an entity as the sum
// of its own position and the position of its ancestors.
// `position` and `parenting` parameters should point to the
// same object.
Vec2i abs_position(IPosition* position, IParenting* parenting)
{
const Vec2i xy = position->xy();
auto parent = parenting->parent();
if (parent)
{
// If the entity has a parent, return the sum of the
// parent position and the entity's local position.
return xy + abs_position(dynamic_cast<IPosition*>(parent),
dynamic_cast<IParenting*>(parent));
}
return xy;
}
... cũng như khá xấu xí / nguy hiểm, vì chúng tôi đã rò rỉ trách nhiệm thực hiện việc truyền dễ bị lỗi sang mã máy khách bằng cách sử dụng các giao diện này và / hoặc truyền cùng một đối tượng làm đối số nhiều lần cho nhiều tham số giống nhau chức năng. Vì vậy, cuối cùng chúng tôi thường muốn thiết kế một giao diện loãng hơn, hợp nhất các mối quan tâm của IParenting
và IPosition
ở một nơi, giống như IGuiElement
hoặc một cái gì đó trở nên dễ bị chồng chéo với các mối quan tâm của các giao diện trực giao cũng sẽ bị cám dỗ để có nhiều chức năng thành viên hơn cùng một lý do "tự túc".
Trộn trách nhiệm so với đúc
Khi thiết kế các giao diện với một trách nhiệm cực kỳ đơn giản, cực kỳ đơn giản, sự cám dỗ thường sẽ là chấp nhận một số giao diện bị bỏ qua hoặc hợp nhất để thực hiện nhiều trách nhiệm (và do đó bước đi trên cả ISP và SRP).
Bằng cách sử dụng cách tiếp cận theo kiểu COM (chỉ là QueryInterface
một phần), chúng tôi chơi theo cách tiếp cận hạ thấp nhưng hợp nhất việc truyền đến một vị trí trung tâm trong cơ sở mã và có thể làm một cái gì đó giống như sau:
// Returns the absolute position of an entity as the sum
// of its own position and the position of its ancestors.
// `obj` should implement `IPosition` and optionally `IParenting`.
Vec2i abs_position(Object* obj)
{
// `Object::query_interface` returns nullptr if the interface is
// not provided by the entity. `Object` is an abstract base class
// inherited by all entities using this interface query system.
IPosition* position = obj->query_interface<IPosition>();
assert(position && "obj does not implement IPosition!");
const Vec2i xy = position->xy();
IParenting* parenting = obj->query_interface<IParenting>();
if (parenting && parenting->parent()->query_interface<IPosition>())
{
// If the entity implements IParenting and has a parent,
// return the sum of the parent position and the entity's
// local position.
return xy + abs_position(parenting->parent());
}
return xy;
}
... Tất nhiên là hy vọng với các trình bao bọc an toàn kiểu và tất cả những gì bạn có thể xây dựng tập trung để có được thứ gì đó an toàn hơn so với con trỏ thô.
Với điều này, sự cám dỗ để thiết kế các giao diện chồng chéo thường được giảm thiểu đến mức tối thiểu. Nó cho phép bạn thiết kế các giao diện với các trách nhiệm rất đơn lẻ (đôi khi chỉ có một chức năng thành viên bên trong) mà bạn có thể trộn và khớp với tất cả những gì bạn thích mà không phải lo lắng về ISP và có được sự linh hoạt của việc gõ giả giả trong thời gian chạy trong C ++ (mặc dù tất nhiên là với sự đánh đổi các hình phạt thời gian chạy để truy vấn các đối tượng để xem liệu chúng có hỗ trợ một giao diện cụ thể không). Phần thời gian chạy có thể quan trọng trong một cài đặt với bộ phát triển phần mềm nơi các chức năng sẽ không có thông tin về thời gian biên dịch của các plugin trước khi thực hiện các giao diện này.
Mẫu
Nếu các mẫu là một khả năng (chúng ta có thông tin về thời gian biên dịch cần thiết mà không bị mất theo thời gian chúng ta nắm giữ một đối tượng, tức là), thì chúng ta chỉ cần làm điều này:
// Returns the absolute position of an entity as the sum
// of its own position and the position of its ancestors.
// `obj` should have `position` and `parent` methods.
template <class Entity>
Vec2i abs_position(Entity& obj)
{
const Vec2i xy = obj.xy();
if (obj.parent())
{
// If the entity has a parent, return the sum of the parent
// position and the entity's local position.
return xy + abs_position(obj.parent());
}
return xy;
}
... tất nhiên trong trường hợp như vậy, parent
phương thức sẽ phải trả về cùng Entity
loại, trong trường hợp đó có lẽ chúng ta muốn tránh các giao diện hoàn toàn (vì chúng thường muốn mất thông tin kiểu để làm việc với các con trỏ cơ sở).
Hệ thống thành phần
Nếu bạn bắt đầu theo đuổi cách tiếp cận kiểu COM hơn từ quan điểm linh hoạt hoặc hiệu suất, bạn sẽ thường kết thúc với một hệ thống thành phần thực thể tương tự như những gì công cụ trò chơi áp dụng trong ngành. Vào thời điểm đó, bạn sẽ hoàn toàn vuông góc với nhiều cách tiếp cận hướng đối tượng, nhưng ECS có thể được áp dụng cho thiết kế GUI (một nơi tôi đã dự tính sử dụng ECS bên ngoài tiêu điểm hướng cảnh, nhưng đã xem xét nó quá muộn giải quyết theo cách tiếp cận kiểu COM để thử ở đó).
Lưu ý rằng giải pháp kiểu COM này hoàn toàn nằm ngoài khả năng của các thiết kế bộ công cụ GUI và ECS thậm chí còn nhiều hơn, do đó, đây không phải là thứ sẽ được hỗ trợ bởi nhiều tài nguyên. Tuy nhiên, nó chắc chắn sẽ cho phép bạn giảm thiểu những cám dỗ để thiết kế giao diện có trách nhiệm chồng chéo đến mức tối thiểu, thường khiến nó không đáng lo ngại.
Cách tiếp cận thực dụng
Tất nhiên, giải pháp thay thế là thư giãn bảo vệ bạn một chút hoặc thiết kế giao diện ở mức chi tiết và sau đó bắt đầu kế thừa chúng để tạo giao diện thô hơn mà bạn sử dụng, giống như IPositionPlusParenting
xuất phát từ cả hai IPosition
vàIParenting
(hy vọng với một cái tên tốt hơn thế). Với các giao diện thuần túy, nó không nên vi phạm ISP nhiều như các cách tiếp cận phân cấp sâu nguyên khối thường được áp dụng (Qt, MFC, v.v., trong đó tài liệu thường cảm thấy cần phải che giấu các thành viên không liên quan với mức độ vi phạm quá mức của ISP với các loại đó của các thiết kế), vì vậy một cách tiếp cận thực dụng có thể chỉ đơn giản là chấp nhận một số chồng chéo ở đây và đó. Tuy nhiên, cách tiếp cận kiểu COM này tránh được nhu cầu tạo giao diện hợp nhất cho mọi kết hợp bạn sẽ sử dụng. Mối quan tâm "tự cung tự cấp" đã được loại bỏ hoàn toàn trong những trường hợp như vậy và điều đó thường sẽ loại bỏ nguồn cám dỗ cuối cùng để thiết kế các giao diện có trách nhiệm chồng chéo muốn chống lại cả SRP và ISP.