Đối với tôi, khi bắt đầu, vấn đề chỉ trở nên rõ ràng khi bạn ngừng xem chúng là những thứ giúp mã của bạn dễ dàng hơn / nhanh hơn để viết - đây không phải là mục đích của chúng. Chúng có một số cách sử dụng:
(Điều này sẽ làm mất đi sự tương tự của pizza, vì không dễ để hình dung ra cách sử dụng này)
Giả sử bạn đang tạo một trò chơi đơn giản trên màn hình và Nó sẽ có những sinh vật mà bạn tương tác.
Trả lời: Chúng có thể làm cho mã của bạn dễ duy trì hơn trong tương lai bằng cách giới thiệu một khớp nối lỏng lẻo giữa mặt trước và mặt sau của bạn.
Bạn có thể viết cái này để bắt đầu, vì sẽ chỉ có những kẻ bị troll:
// This is our back-end implementation of a troll
class Troll
{
void Walk(int distance)
{
//Implementation here
}
}
Mặt trước:
function SpawnCreature()
{
Troll aTroll = new Troll();
aTroll.Walk(1);
}
Hai tuần sau, tiếp thị quyết định bạn cũng cần Orc, khi họ đọc về họ trên twitter, vì vậy bạn sẽ phải làm một cái gì đó như:
class Orc
{
void Walk(int distance)
{
//Implementation (orcs are faster than trolls)
}
}
Mặt trước:
void SpawnCreature(creatureType)
{
switch(creatureType)
{
case Orc:
Orc anOrc = new Orc();
anORc.Walk();
case Troll:
Troll aTroll = new Troll();
aTroll.Walk();
}
}
Và bạn có thể thấy làm thế nào điều này bắt đầu trở nên lộn xộn. Bạn có thể sử dụng một giao diện ở đây để giao diện người dùng của bạn sẽ được viết một lần và (đây là bit quan trọng) đã được kiểm tra và sau đó bạn có thể cắm thêm các mục cuối phía sau theo yêu cầu:
interface ICreature
{
void Walk(int distance)
}
public class Troll : ICreature
public class Orc : ICreature
//etc
Mặt trước là:
void SpawnCreature(creatureType)
{
ICreature creature;
switch(creatureType)
{
case Orc:
creature = new Orc();
case Troll:
creature = new Troll();
}
creature.Walk();
}
Giao diện người dùng bây giờ chỉ quan tâm đến giao diện ICreature - không bận tâm đến việc triển khai nội bộ của một con quỷ hoặc một con Orc, mà chỉ dựa trên thực tế là chúng thực hiện ICreature.
Một điểm quan trọng cần lưu ý khi nhìn vào điều này từ quan điểm này là bạn cũng có thể dễ dàng sử dụng một lớp sinh vật trừu tượng, và từ quan điểm này, điều này có tác dụng tương tự .
Và bạn có thể trích xuất sáng tạo ra một nhà máy:
public class CreatureFactory {
public ICreature GetCreature(creatureType)
{
ICreature creature;
switch(creatureType)
{
case Orc:
creature = new Orc();
case Troll:
creature = new Troll();
}
return creature;
}
}
Và kết thúc trước của chúng tôi sẽ trở thành:
CreatureFactory _factory;
void SpawnCreature(creatureType)
{
ICreature creature = _factory.GetCreature(creatureType);
creature.Walk();
}
Mặt trước bây giờ thậm chí không cần phải tham chiếu đến thư viện nơi Troll và Orc được triển khai (cung cấp nhà máy ở trong một thư viện riêng) - nó không cần biết gì về chúng.
B: Giả sử bạn có chức năng mà chỉ một số sinh vật sẽ có trong cấu trúc dữ liệu đồng nhất của bạn , ví dụ:
interface ICanTurnToStone
{
void TurnToStone();
}
public class Troll: ICreature, ICanTurnToStone
Mặt trước có thể là:
void SpawnCreatureInSunlight(creatureType)
{
ICreature creature;
switch(creatureType)
{
case Orc:
creature = new Orc();
case Troll:
creature = new Troll();
}
creature.Walk();
if (creature is ICanTurnToStone)
{
(ICanTurnToStone)creature.TurnToStone();
}
}
C: Sử dụng để tiêm phụ thuộc
Hầu hết các khung tiêm phụ thuộc dễ dàng làm việc hơn khi có sự khớp nối rất lỏng lẻo giữa mã mặt trước và việc thực hiện phía sau. Nếu chúng tôi lấy ví dụ về nhà máy ở trên và để nhà máy của chúng tôi thực hiện giao diện:
public interface ICreatureFactory {
ICreature GetCreature(string creatureType);
}
Mặt trước của chúng tôi sau đó có thể được tiêm này (ví dụ: bộ điều khiển API MVC) thông qua hàm tạo (thông thường):
public class CreatureController : Controller {
private readonly ICreatureFactory _factory;
public CreatureController(ICreatureFactory factory) {
_factory = factory;
}
public HttpResponseMessage TurnToStone(string creatureType) {
ICreature creature = _factory.GetCreature(creatureType);
creature.TurnToStone();
return Request.CreateResponse(HttpStatusCode.OK);
}
}
Với khung DI của chúng tôi (ví dụ Ninject hoặc Autofac), chúng tôi có thể thiết lập chúng để trong thời gian chạy, một thể hiện của CreatureFactory sẽ được tạo bất cứ khi nào cần một ICreatureFactory trong một hàm tạo - điều này làm cho mã của chúng tôi đẹp và đơn giản.
Điều đó cũng có nghĩa là khi chúng tôi viết một bài kiểm tra đơn vị cho bộ điều khiển của mình, chúng tôi có thể cung cấp một ICreatureFactory bị giả định (ví dụ: nếu việc triển khai cụ thể yêu cầu truy cập DB, chúng tôi không muốn kiểm tra đơn vị của mình phụ thuộc vào điều đó) và dễ dàng kiểm tra mã trong bộ điều khiển của chúng tôi .
D: Có những cách sử dụng khác, ví dụ: bạn có hai dự án A và B vì lý do 'di sản' không được cấu trúc tốt và A có tham chiếu đến B.
Sau đó, bạn tìm thấy chức năng trong B cần gọi một phương thức đã có trong A. Bạn không thể thực hiện bằng cách sử dụng các triển khai cụ thể khi bạn có được một tham chiếu vòng tròn.
Bạn có thể có một giao diện được khai báo trong B rằng lớp trong A sau đó thực hiện. Phương thức của bạn trong B có thể được thông qua một thể hiện của một lớp thực hiện giao diện mà không gặp vấn đề gì, mặc dù đối tượng cụ thể thuộc loại A.