Khi nào bạn sử dụng Mẫu cầu? Nó khác với mẫu Adaptor như thế nào?


154

Có ai đã từng sử dụng Mô hình cầu trong một ứng dụng thế giới thực chưa? Nếu vậy, bạn đã sử dụng nó như thế nào? Là tôi, hay chỉ là Mô hình bộ điều hợp với một chút phụ thuộc tiêm vào hỗn hợp? Liệu nó thực sự xứng đáng với mô hình riêng của nó?


Hãy xem xét chấp nhận một câu trả lời khác nhau cho câu hỏi này. Câu trả lời hiện được chấp nhận là không chính xác và không có ích. Câu trả lời mới là vượt trội hơn nhiều.
jaco0646

Các cuốn sách GOF trả lời câu hỏi này trực tiếp.
jaco0646

Câu trả lời:


76

Một ví dụ cổ điển về mẫu Cầu được sử dụng trong định nghĩa hình dạng trong môi trường UI (xem mục Wikipedia mẫu cầu ). Mẫu cầu là tổng hợp của các mẫu MẫuChiến lược .

Đây là một cái nhìn chung về một số khía cạnh của mẫu Adaptor trong mẫu Bridge. Tuy nhiên, để trích dẫn từ bài viết này :

Thoạt nhìn, mẫu Cầu trông rất giống mẫu Bộ điều hợp trong đó một lớp được sử dụng để chuyển đổi một loại giao diện sang loại khác. Tuy nhiên, mục đích của mẫu Adaptor là làm cho một hoặc nhiều giao diện của lớp trông giống như của một lớp cụ thể. Mẫu cầu được thiết kế để tách giao diện của lớp khỏi triển khai của lớp để bạn có thể thay đổi hoặc thay thế triển khai mà không thay đổi mã máy khách.


1
Cầu không có gì để làm với Mẫu hoặc Chiến lược. Cầu là một mô hình cấu trúc. Mẫu và Chiến lược là các mẫu hành vi.
jaco0646

248

Có sự kết hợp giữa FedericoJohn's câu trả lời .

Khi nào:

                   ----Shape---
                  /            \
         Rectangle              Circle
        /         \            /      \
BlueRectangle  RedRectangle BlueCircle RedCircle

Tái cấu trúc để:

          ----Shape---                        Color
         /            \                       /   \
Rectangle(Color)   Circle(Color)           Blue   Red

6
Tại sao bạn sẽ làm kế thừa cho màu sắc?
vainolo

10
@vainolo vì Màu là giao diện và Màu xanh lam, Đỏ là màu cụ thể
Weltschmerz

3
Đây chỉ là một cấu trúc lại. Mục đích của mẫu cầu: "Tách một sự trừu tượng khỏi việc thực hiện nó để hai cái có thể thay đổi độc lập." Đâu là sự trừu tượng và việc thực hiện ở đâu?
clapas

1
Không phải hình chữ nhật (Màu) là một thứ trừu tượng hơn mà điều BlueRonymous?
Anton Shchastnyi

2
@clapas, Trừu tượng là "Shape.color" của thuộc tính, do đó lớp Red và class Blue được triển khai và giao diện Color là cầu nối.
reco

230

Mẫu cầu là một ứng dụng của lời khuyên cũ, "thích sáng tác hơn kế thừa". Nó trở nên tiện dụng khi bạn phải phân lớp thời gian khác nhau theo cách trực giao với nhau. Nói rằng bạn phải thực hiện một hệ thống phân cấp các hình dạng màu. Bạn sẽ không phân lớp Hình dạng với Hình chữ nhật và Hình tròn và sau đó phân lớp Hình chữ nhật với RedRonymous, BlueRonymous và GreenRonymous và tương tự cho Circle, bạn sẽ không? Bạn muốn nói rằng mỗi Hình dạng một Màu và để thực hiện một hệ thống phân cấp các màu và đó là Mẫu Cầu. Chà, tôi sẽ không thực hiện một "hệ thống phân cấp màu sắc", nhưng bạn hiểu ý ...


1
Xem thêm sơ đồ Anton Shchastnyi dưới đây để có một minh họa đồ họa cho lời giải thích này.
NomadeNumerique

2
Tôi không nghĩ rằng một màu sắc là một ví dụ tốt cho một hệ thống phân cấp thực hiện, nó khá khó hiểu. Có một ví dụ hay về mẫu Cầu trong "Mẫu thiết kế" của GoF, trong đó việc triển khai phụ thuộc vào nền tảng: PM của IBM, UNIX của X, v.v.
clapas

215

Khi nào:

        A
     /     \
    Aa      Ab
   / \     /  \
 Aa1 Aa2  Ab1 Ab2

Tái cấu trúc để:

     A         N
  /     \     / \
Aa(N) Ab(N)  1   2

3
Tôi nghĩ rằng đó là cách tiếp cận rất thực tế đối với các mẫu: 1) mô tả thiết kế chuyển tiếp thẳng tối ưu 2) thiết kế / mã tái cấu trúc để thực hiện tốt hơn
Alexey

1
Sử dụng khái niệm toán học để giải thích mẫu thiết kế Cầu. Rất thích thú.
Jian Huang

1
Đây chỉ là một cấu trúc lại. Mục đích của mẫu cầu: "Tách một sự trừu tượng khỏi việc thực hiện nó để hai cái có thể thay đổi độc lập." Đâu là sự trừu tượng và việc thực hiện ở đâu?
clapas

John đặt nó độc đáo trong một bài viết trên blog . Tìm thấy nó là một đọc tốt cho tổng quan cấp cao.
Vaibhav Bhalla

29

Adaptor và Bridge chắc chắn có liên quan, và sự khác biệt là tinh tế. Có khả năng một số người nghĩ rằng họ đang sử dụng một trong những mẫu này thực sự đang sử dụng mẫu kia.

Giải thích tôi đã thấy là Bộ điều hợp được sử dụng khi bạn đang cố gắng hợp nhất các giao diện của một số lớp không tương thích đã tồn tại . Bộ điều hợp có chức năng như một loại dịch giả cho các triển khai có thể được coi là di sản .

Trong khi đó mẫu Cầu được sử dụng cho mã có nhiều khả năng là trường xanh. Bạn đang thiết kế Bridge để cung cấp giao diện trừu tượng cho việc triển khai cần thay đổi, nhưng bạn cũng xác định giao diện của các lớp thực hiện đó.

Trình điều khiển thiết bị là một ví dụ thường được nhắc đến của Bridge, nhưng tôi nói đó là Cầu nếu bạn đang xác định thông số giao diện cho nhà cung cấp thiết bị, nhưng đó là Bộ điều hợp nếu bạn đang sử dụng trình điều khiển thiết bị hiện có và tạo lớp bọc cho cung cấp một giao diện hợp nhất.

Vì vậy, mã khôn ngoan, hai mẫu rất giống nhau. Kinh doanh khôn ngoan, họ khác biệt.

Xem thêm http://c2.com/cgi/wiki?BridgePotype


Này Bill. Tôi không hiểu tại sao chúng ta nhất thiết phải sử dụng mẫu Cầu trong trình điều khiển thiết bị. Ý tôi là chúng ta có thể dễ dàng ủy thác việc thực hiện (đọc, viết, tìm kiếm, v.v.) cho lớp đúng thông qua đa hình phải không? Hoặc với một khách truy cập có lẽ? Tại sao nó phải là Cầu? Cảm ơn trước.
stdout

1
@zgulser, vâng, bạn có sử dụng đa hình. Mẫu cầu mô tả một loại sử dụng của các lớp con để tách rời việc thực hiện khỏi sự trừu tượng hóa.
Bill Karwin

Bạn có nghĩa là tách rời triển khai Hình dạng (ví dụ: Hình chữ nhật) từ ngày trừu tượng Màu phải không? Và tôi tin rằng bạn đang nói có nhiều cách để làm điều đó và Bridge chỉ là một trong số đó.
stdout

Có, phân lớp có công dụng khác. Cách sử dụng các lớp con đặc biệt này là những gì làm cho nó trở thành mẫu Cầu.
Bill Karwin

Và ý nghĩa của việc tách rời là từ giao diện Hình dạng trừu tượng đến cách triển khai Hình chữ nhật cụ thể. Vì vậy, bạn có thể viết mã cần một đối tượng thuộc loại "Hình dạng", mặc dù đối tượng cụ thể thực sự là một lớp con của Hình dạng.
Bill Karwin

27

Theo kinh nghiệm của tôi, Bridge là một mô hình khá thường xuyên, bởi vì đó là giải pháp bất cứ khi nào có hai kích thước trực giao trong miền . Ví dụ: hình dạng và phương pháp vẽ, hành vi và nền tảng, định dạng tệp và tuần tự hóa, v.v.

Và một lời khuyên: luôn luôn nghĩ về các mẫu thiết kế từ quan điểm khái niệm , không phải từ quan điểm thực hiện. Từ quan điểm đúng đắn, Bridge không thể bị nhầm lẫn với Adaptor, bởi vì chúng giải quyết một vấn đề khác và thành phần vượt trội hơn so với thừa kế không phải vì lợi ích của chính nó, mà vì nó cho phép xử lý các mối quan tâm trực giao một cách riêng biệt.


22

Mục đích của BridgeAdaptor là khác nhau và chúng tôi cần cả hai mẫu riêng biệt.

Mẫu cầu:

  1. Nó là một mô hình cấu trúc
  2. Trừu tượng và thực hiện không bị ràng buộc tại thời gian biên dịch
  3. Trừu tượng hóa và thực hiện - cả hai có thể thay đổi mà không ảnh hưởng đến khách hàng
  4. Sử dụng thành phần trên thừa kế.

Sử dụng mẫu Cầu khi:

  1. Bạn muốn ràng buộc thời gian thực hiện,
  2. Bạn có sự phổ biến của các lớp do giao diện được ghép nối và nhiều triển khai,
  3. Bạn muốn chia sẻ một triển khai giữa nhiều đối tượng,
  4. Bạn cần ánh xạ phân cấp lớp trực giao.

@ John Sonmez trả lời rõ ràng cho thấy hiệu quả của mô hình cầu trong việc giảm phân cấp lớp.

Bạn có thể tham khảo liên kết tài liệu bên dưới để hiểu rõ hơn về mẫu cầu với ví dụ mã

Mẫu bộ điều hợp :

  1. cho phép hai giao diện không liên quan làm việc cùng nhau thông qua các đối tượng khác nhau, có thể đóng vai trò giống nhau.
  2. Nó sửa đổi giao diện ban đầu.

Sự khác biệt chính:

  1. Bộ điều hợp làm cho mọi thứ hoạt động sau khi chúng được thiết kế; Cầu làm cho họ làm việc trước khi họ đang có.
  2. Cầu được thiết kế lên phía trước để cho sự trừu tượng và việc thực hiện thay đổi độc lập . Bộ điều hợp được trang bị thêm để làm cho các lớp không liên quan làm việc cùng nhau.
  3. Mục đích: Adaptor cho phép hai giao diện không liên quan hoạt động cùng nhau. Cầu cho phép Trừu tượng và thực hiện để thay đổi độc lập.

Câu hỏi SE liên quan với sơ đồ UML và mã làm việc:

Sự khác biệt giữa mẫu cầu và mẫu bộ điều hợp

Bài viết hữu ích:

bài viết mô hình cầu chua

bài viết mô hình bộ điều hợp nguồn

cầu journaldevbài viết mô hình

BIÊN TẬP:

Ví dụ về thế giới thực của mẫu cầu nối (Theo đề xuất của meta.stackoverflow.com, ví dụ trang web tài liệu được kết hợp trong bài đăng này vì tài liệu sẽ được đặt trước)

Mô hình cầu tách rời sự trừu tượng khỏi việc thực hiện để cả hai có thể thay đổi độc lập. Nó đã đạt được với thành phần hơn là kế thừa.

Mẫu cầu nối UML từ Wikipedia:

Mẫu cầu nối UML từ Wikipedia

Bạn có bốn thành phần trong mô hình này.

Abstraction: Nó xác định một giao diện

RefinedAbstraction: Nó thực hiện trừu tượng:

Implementor: Nó xác định một giao diện để thực hiện

ConcreteImplementor: Nó thực hiện giao diện Triển khai.

The crux of Bridge pattern :Hai hệ thống phân cấp lớp trực giao sử dụng thành phần (và không kế thừa). Hệ thống phân cấp trừu tượng và phân cấp thực hiện có thể thay đổi độc lập. Thực hiện không bao giờ đề cập đến Trừu tượng. Trừu tượng chứa giao diện triển khai như một thành viên (thông qua thành phần). Thành phần này làm giảm thêm một cấp bậc phân cấp thừa kế.

Trường hợp sử dụng từ thật:

Cho phép các phương tiện khác nhau có cả hai phiên bản của hệ thống hộp số tay và tự động.

Mã ví dụ:

/* Implementor interface*/
interface Gear{
    void handleGear();
}

/* Concrete Implementor - 1 */
class ManualGear implements Gear{
    public void handleGear(){
        System.out.println("Manual gear");
    }
}
/* Concrete Implementor - 2 */
class AutoGear implements Gear{
    public void handleGear(){
        System.out.println("Auto gear");
    }
}
/* Abstraction (abstract class) */
abstract class Vehicle {
    Gear gear;
    public Vehicle(Gear gear){
        this.gear = gear;
    }
    abstract void addGear();
}
/* RefinedAbstraction - 1*/
class Car extends Vehicle{
    public Car(Gear gear){
        super(gear);
        // initialize various other Car components to make the car
    }
    public void addGear(){
        System.out.print("Car handles ");
        gear.handleGear();
    }
}
/* RefinedAbstraction - 2 */
class Truck extends Vehicle{
    public Truck(Gear gear){
        super(gear);
        // initialize various other Truck components to make the car
    }
    public void addGear(){
        System.out.print("Truck handles " );
        gear.handleGear();
    }
}
/* Client program */
public class BridgeDemo {    
    public static void main(String args[]){
        Gear gear = new ManualGear();
        Vehicle vehicle = new Car(gear);
        vehicle.addGear();

        gear = new AutoGear();
        vehicle = new Car(gear);
        vehicle.addGear();

        gear = new ManualGear();
        vehicle = new Truck(gear);
        vehicle.addGear();

        gear = new AutoGear();
        vehicle = new Truck(gear);
        vehicle.addGear();
    }
}

đầu ra:

Car handles Manual gear
Car handles Auto gear
Truck handles Manual gear
Truck handles Auto gear

Giải trình:

  1. Vehicle là một sự trừu tượng.
  2. CarTrucklà hai triển khai cụ thể của Vehicle.
  3. Vehicleđịnh nghĩa một phương thức trừu tượng : addGear().
  4. Gear là giao diện người thực hiện
  5. ManualGearAutoGearlà hai triển khai của Gear
  6. Vehiclechứa implementorgiao diện hơn là thực hiện giao diện. CompositonGiao diện của người triển khai là mấu chốt của mẫu này: Nó cho phép trừu tượng hóa và triển khai thay đổi độc lập.
  7. CarTruckđịnh nghĩa triển khai (trừu tượng được định nghĩa lại) cho trừu tượng addGear():: Nó chứa Gear- Hoặc ManualhoặcAuto

Ca sử dụng cho mẫu Cầu :

  1. Trừu tượngthực hiện có thể thay đổi độc lập lẫn nhau và chúng không bị ràng buộc tại thời gian biên dịch
  2. Bản đồ phân cấp trực giao - Một cho Trừu tượng và một cho Thực hiện .

"Bộ điều hợp làm cho mọi thứ hoạt động sau khi chúng được thiết kế; Bridge làm cho chúng hoạt động trước khi chúng hoạt động." Bạn có thể muốn xem xét Bộ điều hợp cắm. Đó là một biến thể của Bộ điều hợp được mô tả bởi GoF trong phần "Bộ điều hợp" trong sách Mẫu thiết kế của họ. Mục đích là để tạo một giao diện cho các lớp chưa tồn tại. Bộ điều hợp có thể cắm không phải là Cầu, vì vậy tôi không cảm thấy điểm đầu tiên là hợp lệ.
c1moore

Mặc dù bằng tay và tự động thiết bị có thể yêu cầu thực hiện khác nhau cho xe tải và xe
andigor

9

Tôi đã sử dụng mô hình cầu tại nơi làm việc. Tôi lập trình trong C ++, nơi nó thường được gọi là thành ngữ PIMPL (con trỏ để thực hiện). Nó trông như thế này:

class A
{
public: 
  void foo()
  {
    pImpl->foo();
  }
private:
  Aimpl *pImpl;
};

class Aimpl
{
public:
  void foo();
  void bar();
};  

Trong ví dụ class Anày chứa giao diện và class Aimplchứa việc thực hiện.

Một cách sử dụng cho mẫu này là chỉ hiển thị một số thành viên công khai của lớp thực hiện, nhưng không sử dụng cho các thành viên khác. Trong ví dụ chỉ Aimpl::foo()có thể được gọi thông qua giao diện chung của A, nhưng khôngAimpl::bar()

Một ưu điểm khác là bạn có thể xác định Aimpltrong một tệp tiêu đề riêng biệt mà người dùng không cần đưa vào A. Tất cả những gì bạn phải làm là sử dụng khai báo chuyển tiếp Aimpltrước đó Ađược xác định và di chuyển các định nghĩa của tất cả các hàm thành viên tham chiếu pImplvào tệp .cpp. Điều này cung cấp cho bạn khả năng giữ Aimpltiêu đề riêng tư và giảm thời gian biên dịch.


2
Nếu bạn sử dụng mẫu này thì AImpl thậm chí không cần tiêu đề. Tôi chỉ đặt nó nội tuyến trong tệp triển khai cho lớp A
1800 THÔNG TIN

Người thực hiện của bạn là riêng tư. Tôi có một câu hỏi mới liên quan đến vấn đề này, xem stackoverflow.com/questions/17680762/ Khăn
Roland

7

Để đặt ví dụ hình dạng trong mã:

#include<iostream>
#include<string>
#include<cstdlib>

using namespace std;

class IColor
{
public:
    virtual string Color() = 0;
};

class RedColor: public IColor
{
public:
    string Color()
    {
        return "of Red Color";
    }
};

class BlueColor: public IColor
{
public:
    string Color()
    {
        return "of Blue Color";
    }
};


class IShape
{
public:
virtual string Draw() = 0;
};

class Circle: public IShape
{
        IColor* impl;
    public:
        Circle(IColor *obj):impl(obj){}
        string Draw()
        {
            return "Drawn a Circle "+ impl->Color();
        }
};

class Square: public IShape
{
        IColor* impl;
    public:
        Square(IColor *obj):impl(obj){}
        string Draw()
        {
        return "Drawn a Square "+ impl->Color();;
        }
};

int main()
{
IColor* red = new RedColor();
IColor* blue = new BlueColor();

IShape* sq = new Square(red);
IShape* cr = new Circle(blue);

cout<<"\n"<<sq->Draw();
cout<<"\n"<<cr->Draw();

delete red;
delete blue;
return 1;
}

Đầu ra là:

Drawn a Square of Red Color
Drawn a Circle of Blue Color

Lưu ý sự dễ dàng mà màu sắc và hình dạng mới có thể được thêm vào hệ thống mà không dẫn đến sự bùng nổ của các lớp con do hoán vị.


0

Đối với tôi, tôi nghĩ về nó như một cơ chế để bạn có thể trao đổi giao diện. Trong thế giới thực, bạn có thể có một lớp có thể sử dụng nhiều hơn một giao diện, Bridge cho phép bạn trao đổi.


0

Bạn đang làm việc cho một công ty bảo hiểm nơi bạn phát triển một ứng dụng quy trình công việc quản lý các loại nhiệm vụ khác nhau: kế toán, hợp đồng, yêu cầu bồi thường. Đây là sự trừu tượng. Về phía triển khai, bạn phải có khả năng tạo các tác vụ từ các nguồn khác nhau: email, fax, tin nhắn điện tử.

Bạn bắt đầu thiết kế của bạn với các lớp này:

public class Task {...}
public class AccountingTask : Task {...}
public class ContractTask : Task {...}
public class ClaimTask : Task {...}

Bây giờ, vì mỗi nguồn phải được xử lý theo một cách cụ thể, bạn quyết định chuyên môn hóa từng loại nhiệm vụ:

public class EmailAccountingTask : AccountingTask {...}
public class FaxAccountingTask : AccountingTask {...}
public class EmessagingAccountingTask : AccountingTask {...}

public class EmailContractTask : ContractTask {...}
public class FaxContractTask : ContractTask {...}
public class EmessagingContractTask : ContractTask {...}

public class EmailClaimTask : ClaimTask {...}
public class FaxClaimTask : ClaimTask {...}
public class EmessagingClaimTask : ClaimTask {...}

Bạn kết thúc với 13 lớp. Thêm một loại nhiệm vụ hoặc một loại nguồn trở nên thách thức. Sử dụng mẫu cầu tạo ra một cái gì đó dễ bảo trì hơn bằng cách tách rời tác vụ (sự trừu tượng hóa) khỏi nguồn (đó là một mối quan tâm thực hiện):

// Source
public class Source {
   public string GetSender();
   public string GetMessage();
   public string GetContractReference();
   (...)
}

public class EmailSource : Source {...}
public class FaxSource : Source {...}
public class EmessagingSource : Source {...}

// Task
public class Task {
   public Task(Source source);
   (...)
}
public class AccountingTask : Task {...}
public class ContractTask : Task {...}
public class ClaimTask : Task {...}

Thêm một loại nhiệm vụ hoặc một nguồn bây giờ dễ dàng hơn nhiều.

Lưu ý: Hầu hết các nhà phát triển sẽ không tạo ra 13 lớp phân cấp trả trước để xử lý vấn đề này. Tuy nhiên, trong cuộc sống thực, bạn có thể không biết trước số loại nguồn và loại nhiệm vụ; nếu bạn chỉ có một nguồn và hai loại nhiệm vụ, có thể bạn sẽ không tách rời Nhiệm vụ khỏi Nguồn. Sau đó, độ phức tạp tổng thể tăng lên khi các nguồn và loại nhiệm vụ mới được thêm vào. Tại một số điểm, bạn sẽ cấu trúc lại và, thường xuyên nhất, kết thúc bằng một giải pháp giống như cây cầu.


-4
Bridge design pattern we can easily understand helping of service and dao layer.

Dao layer -> create common interface for dao layer ->
public interface Dao<T>{
void save(T t);
}
public class AccountDao<Account> implement Dao<Account>{
public void save(Account){
}
}
public LoginDao<Login> implement Dao<Login>{
public void save(Login){
}
}
Service Layer ->
1) interface
public interface BasicService<T>{
    void save(T t);
}
concrete  implementation of service -
Account service -
public class AccountService<Account> implement BasicService<Account>{
 private Dao<Account> accountDao;
 public AccountService(AccountDao dao){
   this.accountDao=dao;
   }
public void save(Account){
   accountDao.save(Account);
 }
}
login service- 
public class LoginService<Login> implement BasicService<Login>{
 private Dao<Login> loginDao;
 public AccountService(LoginDao dao){
   this.loginDao=dao;
   }
public void save(Login){
   loginDao.save(login);
 }
}

public class BridgePattenDemo{
public static void main(String[] str){
BasicService<Account> aService=new AccountService(new AccountDao<Account>());
Account ac=new Account();
aService.save(ac);
}
}
}

5
Tôi đánh giá thấp bởi vì tôi cảm thấy đây là một câu trả lời phức tạp, được định dạng kém.
Zimano

1
Hoàn toàn đồng ý, làm thế nào bạn có thể đăng câu trả lời trên trang web này mà không cần chú ý tối thiểu đến việc thụt mã và không rõ ràng
Massimiliano Kraus
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.