ทำความเข้าใจกับ“ รูปแบบมัณฑนากร” ด้วยตัวอย่างจริงของโลก


167

ผมได้รับการศึกษาแบบมัณฑนากรเป็นเอกสารในการขาด

โปรดช่วยผมเข้าใจแบบมัณฑนากร ใครช่วยยกตัวอย่างการใช้กรณีที่มีประโยชน์ในโลกแห่งความจริง?


8
คุณสามารถหาตัวอย่างจริงได้ที่นี่ใน Java API: stackoverflow.com/questions/1673841//
BalusC

บทความที่แสดงประโยชน์ของรูปแบบการตกแต่งด้วยตัวอย่างง่าย ๆ : dzone.com/articles/is-inheritance-dead
nbilal

คำตอบ:


226

รูปแบบมัณฑนากรบรรลุเป้าหมายเดียวของการเพิ่มความรับผิดชอบให้กับวัตถุใด ๆ แบบไดนามิก

พิจารณากรณีของร้านพิซซ่า ในร้านพิซซ่าพวกเขาจะขายพิซซ่าไม่กี่สายพันธุ์และพวกเขายังมีรสชาติในเมนู ตอนนี้ลองนึกภาพสถานการณ์ที่ร้านพิซซ่าต้องระบุราคาสำหรับการรวมกันของพิซซ่าและท็อปปิ้ง แม้ว่าจะมีพิซซ่าขั้นพื้นฐานสี่ตัวและท็อปปิ้ง 8 ชนิดที่แตกต่างกันแอปพลิเคชั่นจะบ้าคลั่งที่จะรักษาส่วนผสมที่เป็นรูปธรรมเหล่านี้ของพิซซ่าและท็อปปิ้ง

ที่นี่รูปแบบมัณฑนากรมา

ตามรูปแบบของมัณฑนากรคุณจะใช้ท็อปปิ้งเป็นมัณฑนากรและพิซซ่าจะได้รับการตกแต่งโดยมัณฑนากรของท็อปปิ้ง ในทางปฏิบัติลูกค้าแต่ละรายต้องการรสชาติที่ต้องการและยอดเรียกเก็บเงินสุดท้ายของเขาจะประกอบด้วยพิซซ่าฐานและท็อปปิ้งคำสั่งเพิ่มเติม นักตกแต่งหน้าเครื่องประดับแต่ละคนจะรู้เกี่ยวกับพิซซ่าว่ามันเป็นของตกแต่งและราคา GetPrice () วิธีการติดตามวัตถุจะคืนราคาสะสมของทั้งพิซซ่าและท็อปปิ้ง

แก้ไข

นี่คือตัวอย่างรหัสของคำอธิบายด้านบน

public abstract class BasePizza
{
    protected double myPrice;

    public virtual double GetPrice()
    {
        return this.myPrice;
    }
}

public abstract class ToppingsDecorator : BasePizza
{
    protected BasePizza pizza;
    public ToppingsDecorator(BasePizza pizzaToDecorate)
    {
        this.pizza = pizzaToDecorate;
    }

    public override double GetPrice()
    {
        return (this.pizza.GetPrice() + this.myPrice);
    }
}

class Program
{
    [STAThread]
    static void Main()
    {
        //Client-code
        Margherita pizza = new Margherita();
        Console.WriteLine("Plain Margherita: " + pizza.GetPrice().ToString());

        ExtraCheeseTopping moreCheese = new ExtraCheeseTopping(pizza);
        ExtraCheeseTopping someMoreCheese = new ExtraCheeseTopping(moreCheese);
        Console.WriteLine("Plain Margherita with double extra cheese: " + someMoreCheese.GetPrice().ToString());

        MushroomTopping moreMushroom = new MushroomTopping(someMoreCheese);
        Console.WriteLine("Plain Margherita with double extra cheese with mushroom: " + moreMushroom.GetPrice().ToString());

        JalapenoTopping moreJalapeno = new JalapenoTopping(moreMushroom);
        Console.WriteLine("Plain Margherita with double extra cheese with mushroom with Jalapeno: " + moreJalapeno.GetPrice().ToString());

        Console.ReadLine();
    }
}

public class Margherita : BasePizza
{
    public Margherita()
    {
        this.myPrice = 6.99;
    }
}

public class Gourmet : BasePizza
{
    public Gourmet()
    {
        this.myPrice = 7.49;
    }
}

public class ExtraCheeseTopping : ToppingsDecorator
{
    public ExtraCheeseTopping(BasePizza pizzaToDecorate)
        : base(pizzaToDecorate)
    {
        this.myPrice = 0.99;
    }
}

public class MushroomTopping : ToppingsDecorator
{
    public MushroomTopping(BasePizza pizzaToDecorate)
        : base(pizzaToDecorate)
    {
        this.myPrice = 1.49;
    }
}

public class JalapenoTopping : ToppingsDecorator
{
    public JalapenoTopping(BasePizza pizzaToDecorate)
        : base(pizzaToDecorate)
    {
        this.myPrice = 1.49;
    }
}

104
ไม่ชอบรูปแบบนี้หนึ่งบิต บางทีมันอาจเป็นตัวอย่าง ปัญหาหลักที่ฉันมีกับมันในแง่ของอู๊ดเป็นที่ราดหน้าไม่ได้เป็นพิซซ่า การถามราคาของพิซซ่าที่ใช้กับมันไม่ได้ถูกต้องกับฉัน มันเป็นตัวอย่างที่ละเอียดรอบคอบและละเอียดมากดังนั้นฉันไม่ได้ตั้งใจที่จะทำสิ่งนั้นกับคุณ
Tom W

39
@ TomW ฉันคิดว่าส่วนหนึ่งของปัญหาคือการตั้งชื่อ คลาส "Topping" ทั้งหมดควรเรียกว่า "PizzaWith <Topping>" ตัวอย่างเช่น "PizzaWithMushrooms"
Josh Noe

2
ในความคิดของฉันมัณฑนากรใช้ดีที่สุดเท่าที่จะทำได้ โดยที่ฉันหมายถึง "นักตกแต่งห่อตกแต่ง" น้อยที่สุด ดังนั้นบางทีตัวอย่างนี้อาจจะไม่เหมาะสมที่สุด แต่มันค่อนข้างละเอียดซึ่งเป็นสิ่งที่ดี
thekingoftruth

17
จากมุมมองอื่นนี้ไม่ได้อยู่ใกล้กับ "โลกแห่งความจริง" ในโลกแห่งความเป็นจริงคุณไม่ควรคอมไพล์ใหม่ทุกครั้งที่คุณต้องการเพิ่มเมนูใหม่ (หรือเปลี่ยนราคา) ทอปปิง (ปกติ) ถูกเก็บไว้ในฐานข้อมูลและทำให้การแสดงตัวอย่างข้างต้นไร้ประโยชน์
Stelios Adamantidis

4
^ นี่ ฉันคิดว่านี่คือสิ่งที่รบกวนฉันมาตลอดในขณะที่ศึกษารูปแบบนี้ ถ้าฉันเป็น บริษัท ซอฟต์แวร์และเขียนซอฟต์แวร์ร้านพิซซ่าฉันไม่ต้องการที่จะคอมไพล์และส่งใหม่ทุกครั้ง ฉันต้องการเพิ่มแถวในตารางในแบ็กเอนด์หรือสิ่งที่จะดูแลความต้องการของพวกเขาได้อย่างง่ายดาย อย่างดีกล่าวว่า @ Stiosios Adamantidis ฉันเดาว่ารูปแบบความแข็งแกร่งที่ยิ่งใหญ่ที่สุดแม้ว่าจะเป็นการปรับเปลี่ยนคลาสของบุคคลที่สามแล้ว
Canucklesandwich

33

นี่เป็นตัวอย่างง่ายๆของการเพิ่มพฤติกรรมใหม่ให้กับวัตถุที่มีอยู่แบบไดนามิกหรือรูปแบบมัณฑนากร เนื่องจากลักษณะของภาษาแบบไดนามิกเช่น Javascript รูปแบบนี้กลายเป็นส่วนหนึ่งของภาษาเอง

// Person object that we will be decorating with logging capability
var person = {
  name: "Foo",
  city: "Bar"
};

// Function that serves as a decorator and dynamically adds the log method to a given object
function MakeLoggable(object) {
  object.log = function(property) {
    console.log(this[property]);
  }
}

// Person is given the dynamic responsibility here
MakeLoggable(person);

// Using the newly added functionality
person.log('name');


ง่ายและแม่นยำ! เยี่ยมมาก!
nagendra547

1
ฉันไม่คิดว่าแนวคิดของรูปแบบมัณฑนากรใช้ได้ที่นี่ จริงๆแล้วมันไม่ใช่ลวดลายเลย! ใช่คุณกำลังเพิ่มวิธีการใหม่ที่รันไทม์ และอาจเป็นภายในswitchหรือแบบธรรมดาifคุณจะสามารถอ้างได้ว่านี่เป็นตัวอย่างที่ดีของการเพิ่มพฤติกรรมให้กับคลาส แต่เราต้องการอย่างน้อยสองคลาสเพื่อกำหนดมัณฑนากรและวัตถุตกแต่งในรูปแบบนี้
Iman

1
@Zich ฉันเข้าใจว่าไม่มีมัณฑนากรในตัวอย่าง แต่แก้ไขได้ง่ายด้วยการเพิ่มฟังก์ชั่นที่ทำหน้าที่เป็นมัณฑนากร แต่มีวัตถุตกแต่งในตัวอย่างของฉัน รูปแบบการพูดที่ใดก็ได้ที่คุณต้องการสองชั้นโดยเฉพาะ?
Anurag

18

เป็นที่น่าสังเกตว่าโมเดล Java i / o นั้นเป็นไปตามรูปแบบของมัณฑนากร เลเยอร์ของผู้อ่านนี้อยู่ด้านบนของผู้อ่านที่อยู่ด้านบนของ ... เป็นตัวอย่างที่แท้จริงของมัณฑนากร


มีตัวอย่างอื่นใน API สาธารณะจริงหรือไม่ นี่เป็นสิ่งเดียวที่ฉันรู้
โยสิยาห์ Yoder

ดูเหมือนว่าฟังก์ชั่นการห่อหุ้มในธรรมชาติมีรูปแบบของมัณฑนากรบางประเภทที่ฉันคิดว่ามันคืออะไร?
ฮาร์วีย์หลิน

ตัวอย่างที่ดี !!
nagendra547

8

ตัวอย่าง - สถานการณ์สมมติว่าคุณกำลังเขียนโมดูลการเข้ารหัส การเข้ารหัสนี้สามารถเข้ารหัสไฟล์ที่ชัดเจนโดยใช้ DES - มาตรฐานการเข้ารหัสข้อมูล ในทำนองเดียวกันในระบบคุณสามารถมีการเข้ารหัสเป็น AES - มาตรฐานการเข้ารหัสขั้นสูง นอกจากนี้คุณสามารถมีการรวมกันของการเข้ารหัส - แรก DES แล้ว AES หรือคุณสามารถมี AES ก่อนจากนั้นก็ DES

การสนทนา - คุณจะรับมือสถานการณ์นี้อย่างไร คุณไม่สามารถสร้างวัตถุของชุดค่าผสมดังกล่าว - เช่น - AES และ DES - รวม 4 ชุด ดังนั้นคุณต้องมีวัตถุ 4 ชิ้นสิ่งนี้จะซับซ้อนเนื่องจากประเภทการเข้ารหัสจะเพิ่มขึ้น

วิธีแก้ไข - สร้างสแต็คต่อไป - รวมกันตามความต้องการ - ณ เวลาทำงาน ข้อดีอีกประการของแนวทางสแต็กนี้คือคุณสามารถผ่อนคลายได้อย่างง่ายดาย

นี่คือวิธีแก้ปัญหา - ใน C ++

ประการแรกคุณต้องมีคลาสพื้นฐาน - หน่วยพื้นฐานของสแต็ก คุณคิดว่าเป็นฐานของกองซ้อน ในตัวอย่างนี้มันเป็นไฟล์ที่ชัดเจน มาติดตามความแตกต่างกันเสมอ กำหนดให้เป็นคลาสอินเตอร์เฟสของหน่วยพื้นฐานนี้ ด้วยวิธีนี้คุณสามารถใช้งานได้ตามที่คุณต้องการ นอกจากนี้คุณไม่จำเป็นต้องคิดถึงการพึ่งพาในขณะที่รวมหน่วยพื้นฐานนี้

นี่คือคลาสอินเตอร์เฟส -

class IclearData
{
public:

    virtual std::string getData() = 0;
    virtual ~IclearData() = 0;
};

IclearData::~IclearData()
{
    std::cout<<"Destructor called of IclearData"<<std::endl;
}

ตอนนี้ใช้คลาสอินเทอร์เฟซนี้ -

class clearData:public IclearData
{
private:

    std::string m_data;

    clearData();

    void setData(std::string data)
        {
            m_data = data;
        }

public:

    std::string getData()
    {
        return m_data;
    }

    clearData(std::string data)
    {
        setData(data);
    }

    ~clearData()
    {
        std::cout<<"Destructor of clear Data Invoked"<<std::endl;
    }

};

ทีนี้มาสร้างคลาสนามธรรมของมัณฑนากรซึ่งสามารถขยายเพื่อสร้างรสชาติได้ทุกชนิด - ที่นี่รสชาติคือประเภทการเข้ารหัส คลาสนามธรรมมัณฑนากรนี้เกี่ยวข้องกับคลาสพื้นฐาน ดังนั้นมัณฑนากร "เป็น" ประเภทคลาสอินเตอร์เฟส ดังนั้นคุณต้องใช้การสืบทอด

class encryptionDecorator: public IclearData
{

protected:
    IclearData *p_mclearData;

    encryptionDecorator()
    {
      std::cout<<"Encryption Decorator Abstract class called"<<std::endl;
    }

public:

    std::string getData()
    {
        return p_mclearData->getData();
    }

    encryptionDecorator(IclearData *clearData)
    {
        p_mclearData = clearData;
    }

    virtual std::string showDecryptedData() = 0;

    virtual ~encryptionDecorator() = 0;

};

encryptionDecorator::~encryptionDecorator()
{
    std::cout<<"Encryption Decorator Destructor called"<<std::endl;
}

ทีนี้มาสร้างคลาสมัณฑนากรคอนกรีต - ประเภทการเข้ารหัส - AES -

const std::string aesEncrypt = "AES Encrypted ";

class aes: public encryptionDecorator
{

private:

    std::string m_aesData;

    aes();

public:

    aes(IclearData *pClearData): m_aesData(aesEncrypt)
    {
        p_mclearData = pClearData;
        m_aesData.append(p_mclearData->getData());
    }

    std::string getData()
        {
            return m_aesData;
        }

    std::string showDecryptedData(void)
    {
        m_aesData.erase(0,m_aesData.length());
        return m_aesData;
    }

};

ทีนี้สมมติว่าประเภทมัณฑนากรคือ DES -

const std :: string desEncrypt = "DES เข้ารหัส";

class des: public encryptionDecorator
{

private:

    std::string m_desData;

    des();

public:

    des(IclearData *pClearData): m_desData(desEncrypt)
    {
        p_mclearData = pClearData;
        m_desData.append(p_mclearData->getData());
    }

    std::string getData(void)
        {
            return m_desData;
        }

    std::string showDecryptedData(void)
    {
        m_desData.erase(0,desEncrypt.length());
        return m_desData;
    }

};

มาสร้างรหัสลูกค้าเพื่อใช้คลาสมัณฑนากรนี้ -

int main()
{
    IclearData *pData = new clearData("HELLO_CLEAR_DATA");

    std::cout<<pData->getData()<<std::endl;


    encryptionDecorator *pAesData = new aes(pData);

    std::cout<<pAesData->getData()<<std::endl;

    encryptionDecorator *pDesData = new des(pAesData);

    std::cout<<pDesData->getData()<<std::endl;

    /** unwind the decorator stack ***/
    std::cout<<pDesData->showDecryptedData()<<std::endl;

    delete pDesData;
    delete pAesData;
    delete pData;

    return 0;
}

คุณจะเห็นผลลัพธ์ต่อไปนี้ -

HELLO_CLEAR_DATA
Encryption Decorator Abstract class called
AES Encrypted HELLO_CLEAR_DATA
Encryption Decorator Abstract class called
DES Encrypted AES Encrypted HELLO_CLEAR_DATA
AES Encrypted HELLO_CLEAR_DATA
Encryption Decorator Destructor called
Destructor called of IclearData
Encryption Decorator Destructor called
Destructor called of IclearData
Destructor of clear Data Invoked
Destructor called of IclearData

นี่คือแผนภาพ UML - คลาสของมัน ในกรณีที่คุณต้องการข้ามรหัสและมุ่งเน้นด้านการออกแบบ

ป้อนคำอธิบายรูปภาพที่นี่


1
ตัวอย่างไม่เหมาะกว่าstrategy patternใช่หรือไม่
exexzian

@exzian ใช่นักเรียนของฉันมักจะแนะนำรายการกลยุทธ์ให้ฉันสำหรับปัญหาประเภทนี้และมันก็รู้สึกเหมือนเป็นวิธีที่สะอาดที่สุดสำหรับฉันเช่นกัน
Josiah Yoder

ไม่ด้วยรูปแบบกลยุทธ์คุณไม่สามารถรวมวิธีการเข้ารหัสได้ ก่อนหน้านี้คุณจะต้องสร้างคลาสกลยุทธ์สำหรับชุดค่าผสมที่เป็นไปได้ทุกชุด
deetz

4

รูปแบบมัณฑนากรช่วยให้คุณเปลี่ยนหรือกำหนดค่าการทำงานของวัตถุของคุณโดยผูกมัดกับคลาสย่อยอื่น ๆ ที่คล้ายกันของวัตถุนี้

ตัวอย่างที่ดีที่สุดคือคลาส InputStream และ OutputStream ในแพ็คเกจ java.io

    File file=new File("target","test.txt");
    FileOutputStream fos=new FileOutputStream(file);
    BufferedOutputStream bos=new BufferedOutputStream(fos);
    ObjectOutputStream oos=new ObjectOutputStream(bos);


    oos.write(5);
    oos.writeBoolean(true);
    oos.writeBytes("decorator pattern was here.");


//... then close the streams of course.

ในกรณีนี้การเรียกเชนเริ่มต้นที่ ObjectOutputStream จากนั้นไปจนถึงคลาส File จากนั้นคลาส File จะคืนค่าจากนั้นคลาสย่อยอีกสามคลาสจะเพิ่มพวกเขาทั้งหมดและสุดท้ายมูลค่าของเมธอด ObjectOutputStream คือ ถูกต้องหรือไม่
ฮาร์วีย์หลิน

3

รูปแบบการออกแบบมัณฑนากรใน Java คืออะไร

คำจำกัดความที่เป็นทางการของรูปแบบมัณฑนากรจากหนังสือ GoF (รูปแบบการออกแบบ: องค์ประกอบของซอฟต์แวร์เชิงวัตถุที่ใช้ซ้ำได้, 1995, Pearson Education, Inc. Publishing เป็น Pearson Addison Wesley) กล่าวว่าคุณทำได้

"แนบความรับผิดชอบเพิ่มเติมไปยังวัตถุแบบไดนามิกผู้ตกแต่งเป็นทางเลือกที่ยืดหยุ่นในการทำคลาสย่อยเพื่อขยายฟังก์ชันการทำงาน"

สมมติว่าเรามีพิซซ่าและเราต้องการตกแต่งด้วยท็อปปิ้งเช่นไก่มาซาลาหัวหอมและมอสซาเรลล่าชีส เรามาดูวิธีการใช้ใน Java ...

โปรแกรมเพื่อสาธิตวิธีการใช้ Decorator Design Pattern ใน Java

Pizza.java:

<!-- language-all: lang-html -->

package com.hubberspot.designpattern.structural.decorator;

public class Pizza {

public Pizza() {

}

public String description(){
    return "Pizza";
}

}



package com.hubberspot.designpattern.structural.decorator;

public abstract class PizzaToppings extends Pizza {

public abstract String description();

}

package com.hubberspot.designpattern.structural.decorator;

public class ChickenMasala extends PizzaToppings {

private Pizza pizza;

public ChickenMasala(Pizza pizza) {
    this.pizza = pizza;
}

@Override
public String description() {
    return pizza.description() + " with chicken masala, ";
}

}



package com.hubberspot.designpattern.structural.decorator;

public class MozzarellaCheese extends PizzaToppings {

private Pizza pizza;

public MozzarellaCheese(Pizza pizza) {
    this.pizza = pizza;
}

@Override
public String description() {
    return pizza.description() + "and mozzarella cheese.";
}
}



package com.hubberspot.designpattern.structural.decorator;

public class Onion extends PizzaToppings {

private Pizza pizza;

public Onion(Pizza pizza) {
    this.pizza = pizza;
}

@Override
public String description() {
    return pizza.description() + "onions, ";
}

}



package com.hubberspot.designpattern.structural.decorator;

public class TestDecorator {

public static void main(String[] args) {

    Pizza pizza = new Pizza();

    pizza = new ChickenMasala(pizza);
    pizza = new Onion(pizza);
    pizza = new MozzarellaCheese(pizza);

    System.out.println("You're getting " + pizza.description());

}

}

3

ฉันใช้รูปแบบมัณฑนากรอย่างกว้างขวางในที่ทำงานของฉัน ฉันโพสต์บนบล็อกของฉันเกี่ยวกับวิธีใช้กับการบันทึก


ฉันไม่ชอบที่คุณเพิ่งลิงค์ไปเป็นคำตอบ แต่บทความบล็อกของคุณมีประโยชน์มากจนฉันต้อง upvote :) ตอนนี้ฉันเข้าใจแล้ว ทุกคนมาพร้อมกับพิซซ่าและคุณเป็นตัวอย่างที่สมบูรณ์แบบ
Niklas Raab

2

รูปแบบการตกแต่งภายในช่วยให้คุณเพิ่มพฤติกรรมแบบไดนามิกไปยังวัตถุ

ลองมาตัวอย่างที่คุณต้องสร้างแอพที่คำนวณราคาเบอร์เกอร์ประเภทต่างๆ คุณต้องจัดการเบอร์เกอร์หลากหลายรูปแบบเช่น "ใหญ่" หรือ "กับชีส" ซึ่งแต่ละร้านมีราคาที่สัมพันธ์กับเบอร์เกอร์พื้นฐาน เช่นเพิ่ม $ 10 สำหรับเบอร์เกอร์กับชีสเพิ่มอีก $ 15 สำหรับเบอร์เกอร์ขนาดใหญ่ ฯลฯ

ในกรณีนี้คุณอาจถูกล่อลวงให้สร้างคลาสย่อยเพื่อจัดการสิ่งเหล่านี้ เราอาจแสดงสิ่งนี้ใน Ruby เป็น:

class Burger
  def price
    50
  end
end

class BurgerWithCheese < Burger
  def price
    super + 15
  end
end

ในตัวอย่างด้านบนคลาส BurgerWithCheese สืบทอดมาจาก Burger และแทนที่เมธอด price เพื่อเพิ่ม $ 15 เข้ากับราคาที่กำหนดไว้ใน class super คุณจะสร้างคลาส LargeBurger และกำหนดราคาเทียบกับ Burger แต่คุณต้องกำหนดคลาสใหม่สำหรับการรวมกันของ "ใหญ่" และ "กับชีส"

ตอนนี้จะเกิดอะไรขึ้นถ้าเราต้องเสิร์ฟ "เบอร์เกอร์กับมันฝรั่งทอด"? เรามี 4 คลาสเพื่อจัดการชุดค่าผสมเหล่านี้และเราจะต้องเพิ่มอีก 4 ชั้นเพื่อจัดการชุดค่าผสมทั้งหมด 3 รายการ - "ใหญ่", "กับชีส" และ "พร้อมมันฝรั่งทอด" เราต้องการ 8 คลาสตอนนี้ เพิ่มสถานที่ให้บริการอื่นและเราจะต้อง 16 นี้จะเติบโตเป็น 2 ^ n

ให้ลองกำหนด BurgerDecorator ที่ใช้ในวัตถุ Burger แทน:

class BurgerDecorator
  def initialize(burger)
    self.burger = burger
  end
end

class BurgerWithCheese < BurgerDecorator
  def price
    self.burger.price + 15
  end
end

burger = Burger.new
cheese_burger = BurgerWithCheese.new(burger)
cheese_burger.price   # => 65

ในตัวอย่างด้านบนเราได้สร้างคลาส BurgerDecorator ซึ่งคลาส BurgerWithCheese สืบทอดมา นอกจากนี้เรายังสามารถแสดงรูปแบบ "ใหญ่" โดยการสร้างคลาส LargeBurger ตอนนี้เราสามารถกำหนดเบอร์เกอร์ขนาดใหญ่กับชีสที่รันไทม์เป็น:

b = LargeBurger.new(cheese_burger)
b.price  # => 50 + 15 + 20 = 85

โปรดจำไว้ว่าการใช้การสืบทอดเพื่อเพิ่มรูปแบบ "with fries" จะเกี่ยวข้องกับการเพิ่มคลาสย่อยอีก 4 รายการอย่างไร ด้วยการตกแต่งเราจะสร้าง BurgerWithFries ใหม่หนึ่งคลาสเพื่อจัดการรูปแบบใหม่และจัดการสิ่งนี้ที่รันไทม์ สถานที่ให้บริการใหม่แต่ละแห่งจะต้องใช้มัณฑนากรเพิ่มเติมเพื่อให้ครอบคลุมการเรียงสับเปลี่ยนทั้งหมด

PS นี่เป็นบทความสั้น ๆ ของบทความที่ฉันเขียนเกี่ยวกับการใช้รูปแบบมัณฑนากรในทับทิมซึ่งคุณสามารถอ่านได้หากคุณต้องการดูตัวอย่างโดยละเอียดเพิ่มเติม


2

มัณฑนากร:

  1. เพิ่มพฤติกรรมไปยังวัตถุในเวลาทำงาน การสืบทอดเป็นกุญแจสำคัญในการบรรลุฟังก์ชั่นนี้ซึ่งทั้งข้อดีและข้อเสียของรูปแบบนี้
  2. มันช่วยเพิ่มพฤติกรรมของอินเทอร์เฟซ
  3. มัณฑนากรสามารถดูได้ว่าเป็นคอมโพสิตที่เสื่อมสภาพด้วยองค์ประกอบเดียวเท่านั้น อย่างไรก็ตามมัณฑนากรเพิ่มความรับผิดชอบเพิ่มเติม - มันไม่ได้มีไว้สำหรับการรวมวัตถุ
  4. คลาส Decorator จะประกาศความสัมพันธ์ขององค์ประกอบกับอินเทอร์เฟซ LCD (ระดับต่ำสุดของส่วน) และสมาชิกข้อมูลนี้จะเริ่มต้นได้ในตัวสร้าง
  5. มัณฑนากรได้รับการออกแบบมาเพื่อให้คุณเพิ่มความรับผิดชอบให้กับวัตถุโดยไม่ต้องจัดกลุ่มย่อย

อ้างถึงsourcemakingบทความสำหรับรายละเอียดเพิ่มเติม

มัณฑนากร (Abstract) : เป็นคลาสนามธรรม / อินเตอร์เฟสซึ่งใช้อินเทอร์เฟซคอมโพเนนต์ มันมีองค์ประกอบอินเตอร์เฟซ หากไม่มีคลาสนี้คุณจำเป็นต้องมีคลาสย่อยของ ConcreteDecorators สำหรับชุดค่าผสมที่แตกต่างกัน องค์ประกอบขององค์ประกอบลดชั้นย่อยที่ไม่จำเป็น

ตัวอย่าง JDK:

BufferedInputStream bis = new BufferedInputStream(new FileInputStream(new File("a.txt")));
while(bis.available()>0)
{
        char c = (char)bis.read();
        System.out.println("Char: "+c);;
}

ดูคำถาม SE ด้านล่างสำหรับแผนภาพ UML และตัวอย่างรหัส

รูปแบบมัณฑนากรสำหรับ IO

บทความที่มีประโยชน์:

journaldev

วิกิพีเดีย

ตัวอย่างคำที่แท้จริงของรูปแบบการตกแต่ง: VendingMachineDecoratorได้รับการอธิบาย @

เมื่อใดที่จะใช้รูปแบบมัณฑนากร?

Beverage beverage = new SugarDecorator(new LemonDecorator(new Tea("Assam Tea")));
beverage.decorateBeverage();

beverage = new SugarDecorator(new LemonDecorator(new Coffee("Cappuccino")));
beverage.decorateBeverage();

ในตัวอย่างด้านบนชาหรือกาแฟ (เครื่องดื่ม) ตกแต่งด้วยน้ำตาลและมะนาว


2

รูปแบบมัณฑนากรบรรลุวัตถุประสงค์เดียวของแบบไดนามิกเพิ่มความรับผิดชอบต่อวัตถุใด

Java I / O Modelขึ้นอยู่กับรูปแบบของมัณฑนากร

Java IO เป็นรูปแบบมัณฑนากร


1

มีตัวอย่างใน Wikipedia เกี่ยวกับการตกแต่งหน้าต่างด้วยแถบเลื่อน:

http://en.wikipedia.org/wiki/Decorator_pattern

นี่คืออีกตัวอย่าง 'โลกแห่งความจริง' ของ "สมาชิกในทีมหัวหน้าทีมและผู้จัดการ" ซึ่งแสดงให้เห็นว่ารูปแบบมัณฑนากรไม่สามารถถูกแทนที่ด้วยการสืบทอดง่าย ๆ :

https://zishanbilal.wordpress.com/2011/04/28/design-patterns-by-examples-decorator-pattern/


ลิงค์ Zishan Bilal นั้นยอดเยี่ยม - ตัวอย่างที่ดีที่สุดที่ฉันเคยเห็น
stonedauwg

1

เมื่อสักครู่ที่ฉันได้ปรับเปลี่ยน codebase เป็นรูปแบบการใช้มัณฑนากรดังนั้นฉันจะพยายามอธิบายกรณีการใช้งาน

สมมติว่าเรามีชุดบริการและขึ้นอยู่กับว่าผู้ใช้ได้รับสิทธิ์การใช้งานของบริการเฉพาะเราต้องเริ่มบริการหรือไม่

บริการทั้งหมดมีอินเตอร์เฟซทั่วไป

interface Service {
  String serviceId();
  void init() throws Exception;
  void start() throws Exception;
  void stop() throws Exception;
}

การปรับโครงสร้างพื้นฐานล่วงหน้า

abstract class ServiceSupport implements Service {
  public ServiceSupport(String serviceId, LicenseManager licenseManager) {
    // assign instance variables
  }

  @Override
  public void init() throws Exception {
    if (!licenseManager.isLicenseValid(serviceId)) {
       throw new Exception("License not valid for service");
    }
    // Service initialization logic
  }
}

ถ้าคุณสังเกตอย่างระมัดระวังจะขึ้นอยู่กับServiceSupport LicenseManagerแต่ทำไมมันถึงต้องพึ่งพาLicenseManager? ถ้าเราต้องการบริการพื้นหลังซึ่งไม่จำเป็นต้องตรวจสอบข้อมูลสิทธิ์ใช้งาน ในสถานการณ์ปัจจุบันเราจะต้องฝึกซ้อมLicenseManagerเพื่อกลับtrueใช้บริการพื้นหลัง วิธีการนี้ดูเหมือนจะไม่ดีสำหรับฉัน ตามที่ฉันตรวจสอบใบอนุญาตและตรรกะอื่น ๆ มีมุมฉากซึ่งกันและกัน

ดังนั้นรูปแบบของมัณฑนากรจึงมาช่วยและที่นี่จะเริ่มการรีแฟคเตอร์ด้วย TDD

โพสต์ Refactoring

class LicensedService implements Service {
  private Service service;
  public LicensedService(LicenseManager licenseManager, Service service) {
    this.service = service;
  }

  @Override
  public void init() {
    if (!licenseManager.isLicenseValid(service.serviceId())) {
      throw new Exception("License is invalid for service " + service.serviceId());
    }
    // Delegate init to decorated service
    service.init();
  }

  // override other methods according to requirement
}

// Not concerned with licensing any more :)
abstract class ServiceSupport implements Service {
  public ServiceSupport(String serviceId) {
    // assign variables
  }

  @Override
  public void init() {
    // Service initialization logic
  }
}

// The services which need license protection can be decorated with a Licensed service
Service aLicensedService = new LicensedService(new Service1("Service1"), licenseManager);
// Services which don't need license can be created without one and there is no need to pass license related information
Service aBackgroundService = new BackgroundService1("BG-1");

คบ

  • การทำงานร่วมกันของรหัสได้ดีขึ้น
  • การทดสอบหน่วยง่ายขึ้นโดยไม่ต้องจำลองใบอนุญาตเมื่อทำการทดสอบ ServiceSupport
  • ไม่จำเป็นต้องข้ามการออกใบอนุญาตโดยการตรวจสอบพิเศษสำหรับบริการพื้นหลัง
  • การแบ่งหน้าที่ที่เหมาะสม

1

ลองยกตัวอย่าง PubG ปืนไรเฟิลจู่โจมทำงานได้ดีที่สุดเมื่อใช้การซูม 4x และในขณะที่เรากำลังใช้งานอยู่เราจำเป็นต้องมีผู้ชดเชยและผู้ยับยั้ง มันจะลดการหดตัวและลดเสียงการยิงเช่นเดียวกับเสียงก้อง เราจะต้องใช้คุณสมบัตินี้ซึ่งเราจะอนุญาตให้ผู้เล่นซื้อปืนที่ชื่นชอบและอุปกรณ์เสริมของพวกเขา ผู้เล่นสามารถซื้อปืนหรืออุปกรณ์เสริมบางอย่างหรืออุปกรณ์เสริมทั้งหมดและพวกเขาจะถูกเรียกเก็บเงินตามความเหมาะสม

มาดูกันว่ารูปแบบการตกแต่งภายในใช้ที่นี่:

สมมติว่ามีคนต้องการซื้อ SCAR-L พร้อมกับอุปกรณ์เสริมทั้งสามที่กล่าวถึงข้างต้น

  1. นำ SCAR-L ไปใช้
  2. ตกแต่ง (หรือเพิ่ม) SCAR-L ด้วยวัตถุซูม 4 เท่า
  3. ตกแต่ง SCAR-L ด้วยวัตถุตัวยับยั้ง
  4. ตกแต่ง SCAR-L ด้วยวัตถุคอมเพรสเซอร์
  5. โทรวิธีราคาและให้ตัวแทนวัตถุแต่ละรายการเพิ่มราคาโดยใช้วิธีต้นทุนของอุปกรณ์เสริม

สิ่งนี้จะนำไปสู่แผนภาพคลาสเช่นนี้:

รูปแบบมัณฑนากรในที่ทำงาน

ตอนนี้เราสามารถมีคลาสดังนี้:

public abstract class Gun {     
    private Double cost;    
    public Double getCost() {           
        return cost;        
       }    
    }

public abstract class GunAccessories extends Gun {  }

public class Scarl extends Gun {    
    public Scarl() {            
        cost = 100;
        }   
     }

public class Suppressor extends GunAccessories {        
    Gun gun;        
    public Suppressor(Gun gun) {            
    cost = 5;           
    this.gun = gun;     
    }               
    public double getCost(){            
        return cost + gun.getCost();
    }
}

public class GunShop{   
    public static void main(String args[]){         
    Gun scarl = new Scarl();                
    scarl = new Supressor(scarl);
    System.out.println("Price is "+scarl.getCost());
    }      
}

เราสามารถเพิ่มอุปกรณ์เสริมอื่น ๆ ในทำนองเดียวกันและตกแต่งปืนของเรา

อ้างอิง:

https://nulpointerexception.com/2019/05/05/a-beginner-guide-to-decorator-pattern/


0

รูปแบบการออกแบบมัณฑนากร : รูปแบบนี้ช่วยในการปรับเปลี่ยนลักษณะของวัตถุเมื่อรันไทม์ มันให้รสชาติที่แตกต่างกับวัตถุและให้ความยืดหยุ่นในการเลือกส่วนผสมที่เราต้องการใช้ในรสชาตินั้น

ตัวอย่างชีวิตจริง: ให้บอกว่าคุณมีที่นั่งในห้องโดยสารหลักในเที่ยวบิน ตอนนี้คุณได้รับอนุญาตให้เลือกสิ่งอำนวยความสะดวกหลายอย่างพร้อมที่นั่ง สิ่งอำนวยความสะดวกแต่ละอย่างมีค่าใช้จ่ายของตัวเองที่เกี่ยวข้อง ตอนนี้หากผู้ใช้เลือก Wifi และอาหารพรีเมี่ยมเขา / เธอจะถูกเรียกเก็บเงินสำหรับที่นั่ง + wifi + อาหารพรีเมี่ยม

ป้อนคำอธิบายรูปภาพที่นี่

ในกรณีนี้รูปแบบการออกแบบมัณฑนากรสามารถช่วยเราได้จริงๆ เยี่ยมชมลิงค์ด้านบนเพื่อทำความเข้าใจเพิ่มเติมเกี่ยวกับรูปแบบมัณฑนากรและการนำตัวอย่างจริงมาใช้

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.