C++
Designmönsterimplementering i C ++
Sök…
Introduktion
På den här sidan kan du hitta exempel på hur designmönster implementeras i C ++. För detaljer om dessa mönster kan du kolla in dokumentationsmönstret för designmönster .
Anmärkningar
Ett designmönster är en allmän återanvändbar lösning på ett vanligt förekommande problem inom ett givet sammanhang inom programvarudesign.
Observera mönster
Observer Mönster avsikt är att definiera ett-till-många-beroende mellan objekt så att när ett objekt ändrar tillstånd, meddelas alla beroende och uppdateras automatiskt.
Ämnet och observatörer definierar en-till-många-relationen. Observatörerna är beroende av ämnet så att när ämnets tillstånd förändras, blir observatörerna underrättade. Beroende på anmälan kan observatörerna också uppdateras med nya värden.
Här är exemplet från boken "Designmönster" av Gamma.
#include <iostream>
#include <vector>
class Subject;
class Observer
{
public:
virtual ~Observer() = default;
virtual void Update(Subject&) = 0;
};
class Subject
{
public:
virtual ~Subject() = default;
void Attach(Observer& o) { observers.push_back(&o); }
void Detach(Observer& o)
{
observers.erase(std::remove(observers.begin(), observers.end(), &o));
}
void Notify()
{
for (auto* o : observers) {
o->Update(*this);
}
}
private:
std::vector<Observer*> observers;
};
class ClockTimer : public Subject
{
public:
void SetTime(int hour, int minute, int second)
{
this->hour = hour;
this->minute = minute;
this->second = second;
Notify();
}
int GetHour() const { return hour; }
int GetMinute() const { return minute; }
int GetSecond() const { return second; }
private:
int hour;
int minute;
int second;
};
class DigitalClock: public Observer
{
public:
explicit DigitalClock(ClockTimer& s) : subject(s) { subject.Attach(*this); }
~DigitalClock() { subject.Detach(*this); }
void Update(Subject& theChangedSubject) override
{
if (&theChangedSubject == &subject) {
Draw();
}
}
void Draw()
{
int hour = subject.GetHour();
int minute = subject.GetMinute();
int second = subject.GetSecond();
std::cout << "Digital time is " << hour << ":"
<< minute << ":"
<< second << std::endl;
}
private:
ClockTimer& subject;
};
class AnalogClock: public Observer
{
public:
explicit AnalogClock(ClockTimer& s) : subject(s) { subject.Attach(*this); }
~AnalogClock() { subject.Detach(*this); }
void Update(Subject& theChangedSubject) override
{
if (&theChangedSubject == &subject) {
Draw();
}
}
void Draw()
{
int hour = subject.GetHour();
int minute = subject.GetMinute();
int second = subject.GetSecond();
std::cout << "Analog time is " << hour << ":"
<< minute << ":"
<< second << std::endl;
}
private:
ClockTimer& subject;
};
int main()
{
ClockTimer timer;
DigitalClock digitalClock(timer);
AnalogClock analogClock(timer);
timer.SetTime(14, 41, 36);
}
Produktion:
Digital time is 14:41:36
Analog time is 14:41:36
Här är sammanfattningen av mönstret:
Objekt (
DigitalClock
ellerAnalogClock
objekt) använder ämnesgränssnitten (Attach()
ellerDetach()
) antingen för att prenumerera (registrera) som observatörer eller avsluta prenumerera (ta bort) själva från att vara observatörer (subject.Attach(*this);
subject.Detach(*this);
Varje ämne kan ha många observatörer (
vector<Observer*> observers;
).Alla observatörer måste implementera Observer-gränssnittet. Det här gränssnittet har bara en metod,
Update()
, som blir uppringd när ämnets tillstånd ändras (Update(Subject &)
)Förutom metoderna
Attach()
ochDetach()
implementerar det konkreta ämnet enNotify()
-metod som används för att uppdatera alla aktuella observatörer när tillstånd ändras. Men i det här fallet görs alla i förälderklassen,Subject
(Subject::Attach (Observer&)
,void Subject::Detach(Observer&)
ochvoid Subject::Notify()
.Betongobjektet kan också ha metoder för att ställa in och få sitt tillstånd.
Betongobservatörer kan vara vilken klass som helst som implementerar Observer-gränssnittet. Varje observatör prenumererar (registrerar) med ett konkret ämne för att få uppdatering (
subject.Attach(*this);
).De två objekten i Observer Pattern är löst kopplade , de kan interagera men med lite kunskap om varandra.
Variation:
Signal och slots
Signaler och slots är en språkkonstruktion som introduceras i Qt, vilket gör det enkelt att implementera Observer-mönstret samtidigt som man undviker pannplattkoden. Konceptet är att kontroller (även känd som widgets) kan skicka signaler som innehåller händelsesinformation som kan tas emot av andra kontroller med hjälp av speciella funktioner som kallas slots. Spelet i Qt måste vara en klassmedlem som deklareras som sådan. Signal / slot-systemet passar bra till hur grafiska användargränssnitt är utformade. På liknande sätt kan signal / spårsystemet användas för asynkron I / O (inklusive socklar, rör, seriella enheter etc.) händelsemeddelande eller för att associera timeout-händelser med lämpliga objektinstanser och metoder eller funktioner. Ingen registrering / avregistrering / anropskod behöver skrivas, eftersom Qt's Meta Object Compiler (MOC) automatiskt genererar den nödvändiga infrastrukturen.
C # -språket stöder också en liknande konstruktion, även om den har en annan terminologi och syntax: händelser spelar signalerna, och delegaterna är platserna. Dessutom kan en delegat vara en lokal variabel, ungefär som en funktionspekare, medan en plats i Qt måste vara en klassmedlem som deklareras som sådan.
Adaptermönster
Konvertera gränssnittet för en klass till ett annat gränssnitt som kunder förväntar sig. Adapter (eller Wrapper) låter klasser arbeta tillsammans som annars inte kunde bero på inkompatibla gränssnitt. Adaptermönstrets motivation är att vi kan återanvända befintlig programvara om vi kan modifiera gränssnittet.
Adaptermönster förlitar sig på objektets sammansättning.
Klientanrop fungerar på Adapterobjekt.
Adaptern ringer Adaptee för att utföra operationen.
I STL, stack anpassad från vektorn: När stack exekverar push () gör den underliggande vektorn vector :: push_back ().
Exempel:
#include <iostream>
// Desired interface (Target)
class Rectangle
{
public:
virtual void draw() = 0;
};
// Legacy component (Adaptee)
class LegacyRectangle
{
public:
LegacyRectangle(int x1, int y1, int x2, int y2) {
x1_ = x1;
y1_ = y1;
x2_ = x2;
y2_ = y2;
std::cout << "LegacyRectangle(x1,y1,x2,y2)\n";
}
void oldDraw() {
std::cout << "LegacyRectangle: oldDraw(). \n";
}
private:
int x1_;
int y1_;
int x2_;
int y2_;
};
// Adapter wrapper
class RectangleAdapter: public Rectangle, private LegacyRectangle
{
public:
RectangleAdapter(int x, int y, int w, int h):
LegacyRectangle(x, y, x + w, y + h) {
std::cout << "RectangleAdapter(x,y,x+w,x+h)\n";
}
void draw() {
std::cout << "RectangleAdapter: draw().\n";
oldDraw();
}
};
int main()
{
int x = 20, y = 50, w = 300, h = 200;
Rectangle *r = new RectangleAdapter(x,y,w,h);
r->draw();
}
//Output:
//LegacyRectangle(x1,y1,x2,y2)
//RectangleAdapter(x,y,x+w,x+h)
Sammanfattning av koden:
Klienten tror att han pratar med en
Rectangle
Målet är
Rectangle
. Detta är vad klienten åberopar metoden på.Rectangle *r = new RectangleAdapter(x,y,w,h); r->draw();
Observera att adapterklassen använder flera arv.
class RectangleAdapter: public Rectangle, private LegacyRectangle { ... }
Adapter
RectangleAdapter
låterLegacyRectangle
på begäran (draw()
på enRectangle
) genom att ärva BOTH klasser.LegacyRectangle
klassen har inte samma metoder (draw()
) somRectangle
, menAdapter(RectangleAdapter)
kan taRectangle
metodsamtal och vända om och åberopa metoden påLegacyRectangle
,oldDraw()
.class RectangleAdapter: public Rectangle, private LegacyRectangle { public: RectangleAdapter(int x, int y, int w, int h): LegacyRectangle(x, y, x + w, y + h) { std::cout << "RectangleAdapter(x,y,x+w,x+h)\n"; } void draw() { std::cout << "RectangleAdapter: draw().\n"; oldDraw(); } };
Adapterkonstruktionsmönster översätter gränssnittet för en klass till ett kompatibelt men annorlunda gränssnitt. Så det här liknar proxymönstret genom att det är en enkomponent omslag. Men gränssnittet för adapterklassen och den ursprungliga klassen kan vara olika.
Som vi har sett i exemplet ovan är detta adaptermönster användbart för att exponera ett annat gränssnitt för ett befintligt API för att det ska fungera med annan kod. Genom att använda adaptermönster kan vi också ta heterogena gränssnitt och omvandla dem för att ge en enhetlig API.
Bridge-mönster har en struktur som liknar en objektadapter, men Bridge har en annan avsikt: Det är avsett att skilja ett gränssnitt från dess implementering så att de kan varieras enkelt och oberoende. En adapter är avsedd att ändra gränssnittet för ett befintligt objekt.
Fabriksmönster
Fabriksmönster avkopplar skapandet av objekt och tillåter skapande med namn med hjälp av ett gemensamt gränssnitt:
class Animal{
public:
virtual std::shared_ptr<Animal> clone() const = 0;
virtual std::string getname() const = 0;
};
class Bear: public Animal{
public:
virtual std::shared_ptr<Animal> clone() const override
{
return std::make_shared<Bear>(*this);
}
virtual std::string getname() const override
{
return "bear";
}
};
class Cat: public Animal{
public:
virtual std::shared_ptr<Animal> clone() const override
{
return std::make_shared<Cat>(*this);
}
virtual std::string getname() const override
{
return "cat";
}
};
class AnimalFactory{
public:
static std::shared_ptr<Animal> getAnimal( const std::string& name )
{
if ( name == "bear" )
return std::make_shared<Bear>();
if ( name == "cat" )
return std::shared_ptr<Cat>();
return nullptr;
}
};
Byggmönster med flytande API
Byggmästarmönstret frikopplar skapandet av objektet från själva objektet. Huvudtanken bakom är att ett objekt inte behöver ansvara för sin egen skapelse . Rätt och giltig montering av ett komplext objekt kan vara en komplicerad uppgift i sig, så denna uppgift kan delegeras till en annan klass.
Inspirerad av Email Builder i C # , har jag bestämt mig för att skapa en C ++ -version här. Ett e-postobjekt är inte nödvändigtvis ett väldigt komplicerat objekt , men det kan visa mönstret.
#include <iostream>
#include <sstream>
#include <string>
using namespace std;
// Forward declaring the builder
class EmailBuilder;
class Email
{
public:
friend class EmailBuilder; // the builder can access Email's privates
static EmailBuilder make();
string to_string() const {
stringstream stream;
stream << "from: " << m_from
<< "\nto: " << m_to
<< "\nsubject: " << m_subject
<< "\nbody: " << m_body;
return stream.str();
}
private:
Email() = default; // restrict construction to builder
string m_from;
string m_to;
string m_subject;
string m_body;
};
class EmailBuilder
{
public:
EmailBuilder& from(const string &from) {
m_email.m_from = from;
return *this;
}
EmailBuilder& to(const string &to) {
m_email.m_to = to;
return *this;
}
EmailBuilder& subject(const string &subject) {
m_email.m_subject = subject;
return *this;
}
EmailBuilder& body(const string &body) {
m_email.m_body = body;
return *this;
}
operator Email&&() {
return std::move(m_email); // notice the move
}
private:
Email m_email;
};
EmailBuilder Email::make()
{
return EmailBuilder();
}
// Bonus example!
std::ostream& operator <<(std::ostream& stream, const Email& email)
{
stream << email.to_string();
return stream;
}
int main()
{
Email mail = Email::make().from("[email protected]")
.to("[email protected]")
.subject("C++ builders")
.body("I like this API, don't you?");
cout << mail << endl;
}
För äldre versioner av C ++ kan man bara ignorera std::move
operationen och ta bort && från konverteringsoperatören (även om detta skapar en tillfällig kopia).
Byggaren slutför sitt arbete när den släpper det inbyggda e-postmeddelandet av operator Email&&()
. I det här exemplet är byggmästaren ett tillfälligt objekt och returnerar e-postmeddelandet innan det förstörs. Du kan också använda en uttrycklig operation som Email EmailBuilder::build() {...}
istället för konverteringsoperatören.
Passera byggaren runt
En fantastisk funktion som Builder Pattern tillhandahåller är möjligheten att använda flera aktörer för att bygga ett objekt tillsammans. Detta görs genom att skicka byggaren till de andra aktörerna som kommer att ge varandra lite mer information till det byggda objektet. Detta är speciellt kraftfullt när du bygger en slags fråga, lägger till filter och andra specifikationer.
void add_addresses(EmailBuilder& builder)
{
builder.from("[email protected]")
.to("[email protected]");
}
void compose_mail(EmailBuilder& builder)
{
builder.subject("I know the subject")
.body("And the body. Someone else knows the addresses.");
}
int main()
{
EmailBuilder builder;
add_addresses(builder);
compose_mail(builder);
Email mail = builder;
cout << mail << endl;
}
Designvariant: Mutable object
Du kan ändra designen på detta mönster så att det passar dina behov. Jag ger en variant.
I det givna exemplet är E-postobjektet immutable, det vill säga att det inte kan ändras på egenskaperna eftersom det inte finns någon åtkomst till dem. Detta var en önskad funktion. Om du behöver modifiera objektet efter skapandet måste du ge några inställare till det. Eftersom dessa bosättare skulle dupliceras i byggaren kan du överväga att göra allt i en klass (ingen byggarklass behövs längre). Icke desto mindre skulle jag överväga behovet av att göra det byggda objektet mutabelt i första hand.