"Hej kompis!"

"Hej, Bilaabo!"

"Vi har fortfarande lite tid kvar, så jag ska berätta om tre mönster till."

"Tre till? Hur många är det totalt?"

"Det finns för närvarande dussintals populära mönster, men antalet "framgångsrika lösningar" är obegränsat."

"Jag förstår. Så jag måste lära mig flera dussin mönster?"

"Tills du har riktig programmeringserfarenhet kommer de inte att ge dig mycket."

"Det är bättre att du skaffar dig lite mer erfarenhet och sedan, om ett år, återvända till det här ämnet och försöka förstå dem djupare. Åtminstone ett par dussin av de mest populära designmönstren."

"Det är synd att inte använda någon annans erfarenhet och istället hitta på något för 110:e gången."

"Jag håller med."

"Så låt oss börja."

Adaptermönster (eller omslag).

Mönster: Adapter, Proxy, Bridge - 1

"Föreställ dig att du kommer till Kina och upptäcker att eluttagen följer en annan standard. Hålen är inte runda, utan platta. I det här fallet behöver du en adapter."

"Något liknande kan också hända i programmering. Klasser fungerar på liknande men olika gränssnitt. Så vi måste göra en adapter mellan dem."

"Så här ser det ut:"

Exempel
interface Time
{
 int getSeconds();
 int getMinutes();
 int getHours();
}

interface TotalTime
{
 int getTotalSeconds();
}

"Anta att vi har två gränssnitt: Time  och  TotalTime ."

"Time-gränssnittet låter dig få aktuell tid med metoderna getSeconds (),  getMinutes () och  getHours ()."

" TotalTime- gränssnittet låter dig få antalet sekunder som har gått från midnatt till det aktuella ögonblicket."

"Vad ska vi göra om vi har ett TotalTime- objekt, men vi behöver ett Time- objekt eller vice versa?"

"Vi kan skriva adapterklasser för detta. Till exempel:"

Exempel
 class TotalTimeAdapter implements Time
{
 private TotalTime totalTime;
 public TotalTimeAdapter(TotalTime totalTime)
 {
  this.totalTime = totalTime;
 }

 int getSeconds()
 {
  return totalTime.getTotalSeconds() % 60; // seconds
 }

 int getMinutes()
 {
  return totalTime.getTotalSeconds() / 60; // minutes
 }

 int getHours()
 {
  return totalTime.getTotalSeconds() / (60 * 60); // hours
 }
}
 
Användande
TotalTime totalTime = TimeManager.getCurrentTime();
Time time = new TotalTimeAdapter(totalTime);
System.out.println(time.getHours() + " : " + time.getMinutes () + " : " +time.getSeconds());

"Och en adapter åt andra hållet:"

Exempel
class TimeAdapter implements TotalTime
{
 private Time time;
 public TimeAdapter(Time time)
 {
  this.time = time;
 }

 int getTotalSeconds()
 {
  return time.getHours() * 60 * 60 + time.getMinutes() * 60 + time.getSeconds();
 }
}
Användande
Time time = new Time();
TotalTime totalTime = new TimeAdapter(time);
System.out.println(time.getTotalSeconds());

"Ah. Jag gillar det. Men finns det några exempel?"

"Självklart! Som ett exempel är InputStreamReader en klassisk adapter. Den konverterar en InputStream till en Reader."

"Ibland kallas det här mönstret också en omslag, eftersom den nya klassen "lindar" ett annat objekt."

"Du kan läsa andra intressanta saker här ."

Proxy mönster

"Proxymönstret är lite likt omslagsmönstret. Men dess syfte är inte att konvertera gränssnitt, utan att kontrollera åtkomsten till det ursprungliga objektet som är lagrat i proxyklassen. Dessutom har både originalklassen och proxyn vanligtvis samma gränssnitt, vilket gör det lättare att ersätta ett objekt av den ursprungliga klassen med ett proxyobjekt."

"Till exempel:"

Den riktiga klassens gränssnitt
interface Bank
{
 public void setUserMoney(User user, double money);
 public int getUserMoney(User user);
}
Implementering av den ursprungliga klassen
class CitiBank implements Bank
{
 public void setUserMoney(User user, double money)
 {
  UserDAO.updateMoney(user, money);
 }

 public int getUserMoney(User user)
 {
  return UserDAO.getMoney(user);
 }
}
Implementering av proxyklassen
class BankSecurityProxy implements Bank
{
 private Bank bank;
 public BankSecurityProxy(Bank bank)
 {
  this.bank = bank;
 }
 public void setUserMoney(User user, double money)
 {
  if (!SecurityManager.authorize(user, BankAccounts.Manager))
  throw new SecurityException("User can’t change money value");

  bank.setUserMoney(user, money);
 }

 public int getUserMoney(User user)
 {
  if (!SecurityManager.authorize(user, BankAccounts.Manager))
  throw new SecurityException("User can’t get money value");

  return bank.getUserMoney(user);
 }
}

"I exemplet ovan beskrev vi Bank- gränssnittet och CitiBank -klassen, en implementering av detta gränssnitt."

"Gränssnittet låter dig få eller ändra en användares kontosaldo."

Och sedan skapade vi BankSecurityProxy , som också implementerar Bank- gränssnittet och lagrar en referens till ett annat Bank-gränssnitt. Metoderna för denna klass kontrollerar om användaren är kontoägaren eller en bankchef. Om det inte är det, kastas ett SecurityException."

"Så här fungerar det i praktiken:"

Kod utan säkerhetskontroller:
User user = AuthManager.authorize(login, password);
Bank bank = BankFactory.createUserBank(user);
bank.setUserMoney(user, 1000000);
Kod med säkerhetskontroller:
User user = AuthManager.authorize(login, password);
Bank bank = BankFactory.createUserBank(user);
bank = new BankSecurityProxy(bank);
bank.setUserMoney(user, 1000000);

"I det första exemplet skapar vi ett bankobjekt och anropar dess setUserMoney- metod.

"I det andra exemplet lindar vi in ​​det ursprungliga bankobjektet i ett BankSecurityProxy- objekt. De har samma gränssnitt, så efterföljande kod fortsätter att fungera som den gjorde. Men nu kommer en säkerhetskontroll att utföras varje gång en metod anropas."

"Häftigt!"

"Japp. Du kan ha många sådana fullmakt. Du kan till exempel lägga till en annan proxy som kontrollerar om saldot på kontot är för stort. Bankchefen kan bestämma sig för att sätta in mycket pengar på sitt eget konto och fly till Kuba med pengarna ."

"Dessutom... Skapandet av alla dessa kedjor av objekt kan läggas in i en BankFactory- klass, där du kan aktivera/avaktivera de du behöver."

" BufferedReader fungerar enligt en liknande princip. Det är en Reader , men den gör ytterligare arbete."

"Det här tillvägagångssättet låter dig "sätta ihop" ett objekt med den nödvändiga funktionaliteten från olika "bitar"."

"Åh, jag glömde nästan. Proxies används mycket mer än vad jag just visade dig. Du kan läsa om andra användningsområden här ."

Bro mönster

Mönster: Adapter, Proxy, Bridge - 2

"Ibland när ett program körs är det nödvändigt att avsevärt ändra ett objekts funktionalitet. Anta till exempel att du har ett spel med en åsnekaraktär som senare förvandlas till en drake av en magiker. Draken har ett helt annat beteende och egenskaper, men det är samma föremål!"

"Kan vi inte bara skapa ett nytt objekt och bli klara med det?"

"Inte alltid. Anta att din åsna är vän med ett gäng karaktärer, eller så kanske den var under påverkan av flera trollformler, eller så var den inblandad i vissa uppdrag. Med andra ord kan objektet redan vara i bruk på många ställen - och länkade till många andra objekt. Så i det här fallet är det inte ett alternativ att bara skapa ett nytt objekt."

"Jaha, vad kan man göra då?"

"Bronmönstret är en av de mest framgångsrika lösningarna."

"Detta mönster innebär att ett objekt delas upp i två objekt: ett «gränssnittsobjekt» och ett «implementeringsobjekt».

"Vad är skillnaden mellan gränssnittet och klassen som implementerar det?"

"Med ett gränssnitt och en klass slutar vi med ett objekt. Men här - vi har två. Titta på det här exemplet:"

Exempel
class User
{
 private UserImpl realUser;

 public User(UserImpl impl)
 {
  realUser = impl;
 }

 public void run() //Run
 {
  realUser.run();
 }

 public void fly() //Fly
 {
  realUser.fly();
 }
}

class UserImpl
{
 public void run()
 {
 }

 public void fly()
 {
 }
}

"Och sedan kan du deklarera flera underklasser av UserImpl, till exempel UserDonkey (åsna) och UserDragon (drake)."

"Ändå förstår jag inte riktigt hur det här kommer att fungera."

"Tja, något sånt här:"

Exempel
class User
{
 private UserImpl realUser;

 public User(UserImpl impl)
 {
  realUser = impl;
 }

 public void transformToDonkey()
 {
  realUser = new UserDonkeyImpl();
 }

 public void transformToDragon()
 {
  realUser = new UserDragonImpl();
 }
}
Hur det fungerar
User user = new User(new UserDonkey()); // Internally, we're a donkey
user.transformToDragon(); // Now we're a dragon internally

"Så det är ungefär som en proxy."

"Ja, men i en proxy kan huvudobjektet lagras någonstans separat, och koden fungerar med proxyer istället. Här säger vi att alla arbetar med huvudobjektet, men dess delar förändras internt."

"Ah. Tack. Kan du ge mig en länk för att läsa mer om det?"

"Självklart, Amigo, min vän. Varsågod: Bromönster ."