มีใครเคยใช้รูปแบบบริดจ์ในแอปพลิเคชันโลกแห่งความจริงหรือไม่? ถ้าใช่คุณใช้มันอย่างไร? มันเป็นของฉันหรือว่าเป็นเพียงรูปแบบของอะแดปเตอร์ที่มีการฉีดขึ้นรูปเล็ก ๆ มันสมควรได้รับรูปแบบของตัวเองจริงๆเหรอ?
มีใครเคยใช้รูปแบบบริดจ์ในแอปพลิเคชันโลกแห่งความจริงหรือไม่? ถ้าใช่คุณใช้มันอย่างไร? มันเป็นของฉันหรือว่าเป็นเพียงรูปแบบของอะแดปเตอร์ที่มีการฉีดขึ้นรูปเล็ก ๆ มันสมควรได้รับรูปแบบของตัวเองจริงๆเหรอ?
คำตอบ:
ตัวอย่างคลาสสิกของรูปแบบบริดจ์ใช้ในการกำหนดรูปร่างในสภาพแวดล้อม UI (ดูรายการ Wikipedia ของรูปแบบบริดจ์ ) รูปแบบสะพานเป็นคอมโพสิตของแม่แบบและกลยุทธ์รูปแบบ
มันเป็นมุมมองทั่วไปบางประการของรูปแบบตัวแปลงในรูปแบบบริดจ์ อย่างไรก็ตามการอ้างอิงจากบทความนี้ :
ตั้งแต่แรกเห็นรูปแบบบริดจ์จะดูเหมือนรูปแบบของอะแดปเตอร์ในคลาสที่ใช้ในการแปลงอินเทอร์เฟซชนิดหนึ่งไปเป็นอีกประเภทหนึ่ง อย่างไรก็ตามเจตนาของรูปแบบของการ์ดเชื่อมต่อคือการทำให้อินเทอร์เฟซของคลาสตั้งแต่หนึ่งชั้นขึ้นไปดูเหมือนกับคลาสเฉพาะ รูปแบบบริดจ์ถูกออกแบบมาเพื่อแยกอินเทอร์เฟซของคลาสออกจากการใช้งานเพื่อให้คุณสามารถเปลี่ยนแปลงหรือแทนที่การใช้งานได้โดยไม่ต้องเปลี่ยนรหัสลูกค้า
มีการรวมกันของคำตอบของFedericoและจอห์น
เมื่อไหร่:
----Shape---
/ \
Rectangle Circle
/ \ / \
BlueRectangle RedRectangle BlueCircle RedCircle
Refactor ถึง:
----Shape--- Color
/ \ / \
Rectangle(Color) Circle(Color) Blue Red
รูปแบบสะพานเป็นแอปพลิเคชั่นของคำแนะนำแบบเก่า "ชอบองค์ประกอบมากกว่าการสืบทอด" มันจะมีประโยชน์เมื่อคุณต้อง subclass เวลาที่แตกต่างในรูปแบบที่เป็นมุมฉากกับคนอื่น สมมติว่าคุณต้องใช้ลำดับชั้นของรูปร่างที่มีสี คุณจะไม่รูปร่างย่อยด้วยสี่เหลี่ยมผืนผ้าและวงกลมจากนั้นสี่เหลี่ยมผืนผ้าย่อยด้วย RedRectangle, BlueRectangle และ GreenRectangle เหมือนกันสำหรับ Circle คุณจะได้ไหม คุณต้องการบอกว่าแต่ละ Shape มีสีและใช้ลำดับชั้นของสีและนั่นคือรูปแบบบริดจ์ ฉันจะไม่ใช้ "ลำดับชั้นของสี" แต่คุณได้รับแนวคิด ...
เมื่อไหร่:
A
/ \
Aa Ab
/ \ / \
Aa1 Aa2 Ab1 Ab2
Refactor ถึง:
A N
/ \ / \
Aa(N) Ab(N) 1 2
อะแดปเตอร์และสะพานเกี่ยวข้องกันอย่างแน่นอนและความแตกต่างนั้นบอบบาง อาจเป็นไปได้ว่าบางคนที่คิดว่าพวกเขากำลังใช้หนึ่งในรูปแบบเหล่านี้จริง ๆ แล้วใช้รูปแบบอื่น
คำอธิบายที่ผมเคยเห็นก็คือว่าอะแดปเตอร์จะใช้เมื่อคุณกำลังพยายามที่จะรวมการเชื่อมต่อของชั้นเรียนเข้ากันไม่ได้บางอย่างที่มีอยู่แล้ว ฟังก์ชั่นอะแดปเตอร์เป็นชนิดของการแปลเพื่อการใช้งานที่อาจได้รับการพิจารณาเป็นมรดก
ในขณะที่รูปแบบบริดจ์ใช้สำหรับรหัสที่มีแนวโน้มที่จะเป็นสีเขียวมากกว่า คุณกำลังออกแบบ Bridge เพื่อให้มีอินเทอร์เฟซที่เป็นนามธรรมสำหรับการใช้งานที่ต้องการเปลี่ยนแปลง แต่คุณยังกำหนดอินเทอร์เฟซของคลาสการใช้งานเหล่านั้น
Device drivers เป็นตัวอย่างที่อ้างถึงบ่อยครั้งของ Bridge แต่ผมว่ามันเป็น Bridge ถ้าคุณกำลังกำหนดสเป็กอินเทอร์เฟซสำหรับผู้จำหน่ายอุปกรณ์ แต่มันเป็นอะแดปเตอร์หากคุณกำลังใช้ไดรเวอร์อุปกรณ์ที่มีอยู่แล้ว จัดเตรียมอินเตอร์เฟสแบบรวม
รหัสฉลาดทั้งสองรูปแบบจะคล้ายกันมาก ธุรกิจที่ชาญฉลาดพวกเขาแตกต่างกัน
ดูเพิ่มเติมที่http://c2.com/cgi/wiki?BridgePattern
จากประสบการณ์ของผมสะพานเป็นรูปแบบที่เกิดขึ้นค่อนข้างบ่อยเพราะมันเป็นวิธีการแก้ปัญหาเมื่อใดก็ตามที่มีสองมิติฉากในโดเมน เช่นรูปร่างและวิธีการวาด, พฤติกรรมและแพลตฟอร์ม, รูปแบบไฟล์และซีเรียลไลเซอร์เป็นต้น
และคำแนะนำ: คิดถึงรูปแบบการออกแบบจากมุมมองแนวคิดไม่ใช่จากมุมมองการนำไปปฏิบัติ จากมุมมองที่ถูกต้องบริดจ์ไม่สามารถสับสนกับอะแดปเตอร์ได้เพราะพวกเขาแก้ปัญหาที่แตกต่างกันและการจัดองค์ประกอบนั้นดีกว่าการสืบทอดไม่ใช่เพราะเห็นแก่ตัวมันเอง
จุดประสงค์ของBridgeและAdaptorนั้นแตกต่างกันและเราต้องการทั้งสองรูปแบบแยกจากกัน
รูปแบบสะพาน:
ใช้รูปแบบบริดจ์เมื่อ:
คำตอบ @ John Sonmez แสดงให้เห็นอย่างชัดเจนถึงประสิทธิภาพของรูปแบบสะพานในการลดลำดับชั้นของชั้นเรียน
คุณสามารถอ้างอิงลิงค์เอกสารด้านล่างเพื่อรับข้อมูลเชิงลึกเกี่ยวกับรูปแบบบริดจ์พร้อมตัวอย่างโค้ดได้ดียิ่งขึ้น
รูปแบบของอะแดปเตอร์ :
ความแตกต่างที่สำคัญ:
คำถาม SE ที่เกี่ยวข้องกับแผนภาพ UML และรหัสการทำงาน:
ความแตกต่างระหว่างรูปแบบบริดจ์และรูปแบบของการ์ดเชื่อมต่อ
บทความที่มีประโยชน์:
บทความรูปแบบบริดจ์ซอร์สโค้ด
บทความรูปแบบของอะแดปเตอร์ sourcemaking
บทความรูปแบบสะพาน journaldev
แก้ไข:
ตัวอย่างบริดจ์แบบโลกแห่งความจริง (ตามข้อเสนอแนะ meta.stackoverflow.com ตัวอย่างเว็บไซต์เอกสารที่รวมอยู่ในโพสต์นี้เนื่องจากเอกสารกำลังจะไปถึงดวงอาทิตย์)
รูปแบบบริดจ์จะแยกสิ่งที่เป็นนามธรรมออกจากการดำเนินการเพื่อให้ทั้งสองสามารถเปลี่ยนแปลงได้อย่างอิสระ มันประสบความสำเร็จกับองค์ประกอบมากกว่ามรดก
รูปแบบสะพาน UML จาก Wikipedia:
คุณมีสี่องค์ประกอบในรูปแบบนี้
Abstraction
: มันกำหนดอินเทอร์เฟซ
RefinedAbstraction
: มันใช้สิ่งที่เป็นนามธรรม:
Implementor
: มันกำหนดอินเทอร์เฟซสำหรับการใช้งาน
ConcreteImplementor
: ใช้ส่วนต่อประสาน Implementor
The crux of Bridge pattern :
ลำดับชั้นคลาสสองมุมฉากใช้องค์ประกอบ (และไม่มีการสืบทอด) ลำดับชั้นของนามธรรมและลำดับชั้นการนำไปปฏิบัติสามารถแตกต่างกันอย่างอิสระ การดำเนินการไม่เคยหมายถึงสิ่งที่เป็นนามธรรม สิ่งที่เป็นนามธรรมประกอบด้วยส่วนต่อประสานการนำไปใช้งานในฐานะสมาชิก (ผ่านองค์ประกอบ) องค์ประกอบนี้จะลดลำดับชั้นการสืบทอดอีกระดับหนึ่ง
กรณีการใช้คำจริง:
เปิดใช้งานยานพาหนะต่าง ๆ เพื่อให้มีทั้งระบบเกียร์ธรรมดาและเกียร์อัตโนมัติ
รหัสตัวอย่าง:
/* 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();
}
}
เอาท์พุท:
Car handles Manual gear
Car handles Auto gear
Truck handles Manual gear
Truck handles Auto gear
คำอธิบาย:
Vehicle
เป็นสิ่งที่เป็นนามธรรม Car
และTruck
การใช้งานที่เป็นรูปธรรมสองประการVehicle
คือVehicle
addGear()
กำหนดวิธีนามธรรม:Gear
เป็นอินเตอร์เฟสของ implementorManualGear
และAutoGear
มีการใช้งานสองอย่างของ Gear
Vehicle
มีimplementor
ส่วนต่อประสานแทนที่จะใช้ส่วนต่อประสาน Compositon
อินเทอร์เฟซของ implementor คือปมของรูปแบบนี้: มันช่วยให้นามธรรมและการใช้งานแตกต่างกันไปอย่างอิสระ Car
และTruck
กำหนดการใช้งาน (Abstraction ที่นิยามใหม่) สำหรับ Abstraction: addGear()
ประกอบด้วย Gear
- Either Manual
หรือAuto
ใช้กรณีสำหรับรูปแบบบริดจ์ :
ฉันใช้รูปแบบบริดจ์ในที่ทำงาน ฉันเขียนโปรแกรมใน C ++ ซึ่งมักเรียกว่า PIMPL idiom (ตัวชี้ไปยังการนำไปใช้) ดูเหมือนว่านี้:
class A
{
public:
void foo()
{
pImpl->foo();
}
private:
Aimpl *pImpl;
};
class Aimpl
{
public:
void foo();
void bar();
};
ในตัวอย่างclass A
นี้มีอินเตอร์เฟสและclass Aimpl
มีการนำไปใช้งาน
สิ่งหนึ่งที่ใช้สำหรับรูปแบบนี้คือการเปิดเผยเพียงบางส่วนของสมาชิกสาธารณะของชั้นเรียนการใช้งาน แต่ไม่อื่น ๆ ในตัวอย่างAimpl::foo()
สามารถเรียกผ่านส่วนต่อประสานสาธารณะA
ได้ แต่ไม่ใช่Aimpl::bar()
ประโยชน์อีกประการหนึ่งคือการที่คุณสามารถกำหนดในส่วนหัวของแฟ้มที่แยกต่างหากที่ไม่จำเป็นต้องรวมโดยผู้ใช้ของAimpl
A
สิ่งที่คุณต้องทำคือใช้การประกาศล่วงหน้าAimpl
ก่อนA
กำหนดและย้ายนิยามของฟังก์ชันสมาชิกทั้งหมดที่อ้างอิงpImpl
ไปยังไฟล์. cpp สิ่งนี้ให้ความสามารถในการรักษาAimpl
ส่วนหัวและลดเวลาในการคอมไพล์
ในการใส่ตัวอย่างรูปร่างในรหัส:
#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;
}
ผลลัพธ์คือ:
Drawn a Square of Red Color
Drawn a Circle of Blue Color
สังเกตความง่ายในการเพิ่มสีและรูปร่างใหม่ให้กับระบบโดยไม่นำไปสู่การระเบิดของคลาสย่อยเนื่องจากการเรียงสับเปลี่ยน
สำหรับฉันฉันคิดว่ามันเป็นกลไกที่คุณสามารถสลับส่วนต่อประสานได้ ในโลกแห่งความเป็นจริงคุณอาจมีคลาสที่สามารถใช้มากกว่าหนึ่งอินเตอร์เฟสบริดจ์ช่วยให้คุณสลับได้
คุณกำลังทำงานให้กับ บริษัท ประกันภัยที่คุณพัฒนาแอปพลิเคชันเวิร์กโฟลว์ที่จัดการงานประเภทต่างๆ: การบัญชีสัญญาการเรียกร้อง นี่คือสิ่งที่เป็นนามธรรม ในด้านการใช้งานคุณจะต้องสามารถสร้างงานจากแหล่งต่าง ๆ ได้เช่นอีเมล, แฟกซ์, อีเมล
คุณเริ่มต้นการออกแบบด้วยคลาสเหล่านี้:
public class Task {...}
public class AccountingTask : Task {...}
public class ContractTask : Task {...}
public class ClaimTask : Task {...}
ตอนนี้เนื่องจากต้องจัดการแต่ละแหล่งข้อมูลด้วยวิธีเฉพาะคุณตัดสินใจที่จะชำนาญแต่ละประเภทงาน:
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 {...}
คุณจบด้วย 13 คลาส การเพิ่มประเภทของงานหรือประเภทของแหล่งที่มากลายเป็นความท้าทาย การใช้รูปแบบบริดจ์สร้างสิ่งที่ง่ายต่อการดูแลรักษาโดยการแยกงาน (สิ่งที่เป็นนามธรรม) ออกจากแหล่งที่มา (ซึ่งเป็นข้อกังวลของการนำไปใช้):
// 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 {...}
การเพิ่มประเภทงานหรือแหล่งที่มานั้นง่ายขึ้นมาก
หมายเหตุ: นักพัฒนาส่วนใหญ่จะไม่สร้างลำดับชั้นของลำดับชั้น 13 ชั้นเพื่อจัดการปัญหานี้ อย่างไรก็ตามในชีวิตจริงคุณอาจไม่ทราบจำนวนแหล่งที่มาและประเภทของงานล่วงหน้า หากคุณมีเพียงหนึ่งงานและสองประเภทงานคุณอาจไม่แยกงานจากแหล่งที่มา จากนั้นความซับซ้อนโดยรวมจะเพิ่มขึ้นเมื่อมีการเพิ่มแหล่งที่มาและประเภทงานใหม่ เมื่อถึงจุดหนึ่งคุณจะได้รับการปรับโครงสร้างและส่วนใหญ่มักจะจบลงด้วยการแก้ปัญหาสะพาน
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);
}
}
}