Apa perbedaan antara metode templat dan pola strategi?


161

Dapatkah seseorang tolong jelaskan kepada saya apa perbedaan antara pola metode templat dan pola strategi?

Sejauh yang saya tahu mereka 99% sama - satu-satunya perbedaan adalah bahwa pola metode template memiliki kelas abstrak sebagai kelas dasar sedangkan kelas strategi menggunakan antarmuka yang diimplementasikan oleh setiap kelas strategi konkret.

Namun, sejauh menyangkut klien mereka dikonsumsi dengan cara yang persis sama - apakah ini benar?


2
Posting ini dalam SO memiliki jawaban yang lebih baik untuk pertanyaan yang sama: stackoverflow.com/questions/464524/...
Gob00st

12
Pertanyaan yang dikaitkan dengan gob00st adalah perbedaan antara strategi dan jembatan. Sama sekali bukan jawaban untuk pertanyaan ini.
bluekeys

Jawaban:


135

Perbedaan utama antara keduanya adalah ketika algoritma konkret dipilih.

Dengan pola metode Templat ini terjadi pada waktu kompilasi dengan subklasifikasi templat. Setiap subclass menyediakan algoritma konkret yang berbeda dengan menerapkan metode abstrak template. Ketika klien memanggil metode antarmuka eksternal templat, templat memanggil metode abstraknya (antarmuka internal) sebagaimana diperlukan untuk mengaktifkan algoritme.

class ConcreteAlgorithm : AbstractTemplate
{
    void DoAlgorithm(int datum) {...}
}

class AbstractTemplate
{
    void run(int datum) { DoAlgorithm(datum); }

    virtual void DoAlgorithm() = 0; // abstract
}

Sebaliknya, pola Strategi memungkinkan algoritma dipilih pada saat runtime oleh kontainmen . Algoritma konkret diimplementasikan oleh kelas atau fungsi terpisah yang diteruskan ke strategi sebagai parameter untuk konstruktornya atau metode penyetel. Algoritma mana yang dipilih untuk parameter ini dapat bervariasi secara dinamis berdasarkan status atau input program.

class ConcreteAlgorithm : IAlgorithm
{
    void DoAlgorithm(int datum) {...}
}

class Strategy
{
    Strategy(IAlgorithm algo) {...}

    void run(int datum) { this->algo.DoAlgorithm(datum); }
}

Singkatnya:

  • Pola metode templat: pemilihan algoritme waktu kompilasi dengan subkelas
  • Pola strategi: pemilihan algoritma run-time berdasarkan penahanan

47
Kedua pola mendukung pemilihan runtime dari algoritma yang digunakan (untuk Metode Templat, Anda akan melakukan sesuatu seperti if (config.useAlgoA) impl = new AlgoA() else impl = new AlgoB()) sehingga jawaban ini salah.
Borek Bernard

13
Tentu Anda bisa melakukannya tetapi kemudian Anda tidak menggunakan Pola Templat. Pada kenyataannya, hampir persis seperti apa kode yang menciptakan instance Strategi akan terlihat seperti!
thehouse

21
-1, saya pikir jawaban ini (meskipun tidak sepenuhnya salah), melewatkan titik di mana perbedaan sebenarnya. @ tvanfosson jawaban jauh lebih baik.
Doc Brown

1
@Karoly Nyisztor Mereka berdua dapat "mengganti perilaku" dan "memberikan poin ekstensi." Apakah sesuatu itu perilaku atau ekstensi, itu benar-benar tergantung pada konteks di mana Anda menerapkan pola yang diberikan. Anda bisa menyebut setiap subkelas dari pola metode templat sebagai "strategi" juga, atau menyebut setiap kelas strategi dalam pola strategi sebagai "ekstensi," itu hanya kata-kata. Faktanya adalah mereka melakukan hal yang sama KECUALI perbedaan yang disebutkan oleh jawaban ini. Jadi ini jawaban yang tepat.
Andy

1
Algoritma konkret dipilih dengan cara yang sama untuk kedua pola. Pilihan dibuat dengan memohon new ConcreteAlgorithm1()lawan new ConcreteAlgorithm2(). Tentunya pilihan terjadi pada saat runtime (membuat pilihan algoritma pada waktu kompilasi akan berarti mengkodekannya). Perbedaan utama antara keduanya adalah bagaimana algoritma konkret diimplementasikan. Apakah ini diterapkan sebagai subkelas atau sebagai antarmuka terpisah? Yang pertama adalah Templat. Yang terakhir adalah Strategi. Perbedaannya dapat diringkas sebagai komposisi vs warisan, yang merupakan tema umum dari buku GoF.
jaco0646

138

Pola templat digunakan ketika operasi tertentu memiliki beberapa perilaku invarian yang dapat didefinisikan dalam hal perilaku primitif yang berbeda-beda. Kelas abstrak mendefinisikan perilaku invarian (s), sedangkan kelas implementasi mendefinisikan metode dependen.

Dalam sebuah strategi, implementasi perilaku independen - setiap kelas implementasi mendefinisikan perilaku dan tidak ada kode yang dibagikan di antara mereka. Keduanya adalah pola perilaku dan, dengan demikian, dikonsumsi dengan cara yang sama oleh klien. Biasanya strategi memiliki satu metode publik - execute()metode tersebut, sedangkan templat dapat menentukan sekumpulan metode publik serta sekumpulan primitif pribadi pendukung yang harus diimplementasikan oleh subkelas.

Kedua pola tersebut dapat dengan mudah digunakan bersama. Anda mungkin memiliki pola strategi di mana beberapa implementasi milik keluarga strategi diimplementasikan menggunakan pola templat.


Ini kedengarannya benar bagi saya, namun mengapa WikiPedia menyebutkan bahwa "pola strategi adalah untuk perilaku algoritma yang akan dipilih saat runtime"? Itu juga dapat digunakan untuk memilih perilaku algoritma pada waktu kompilasi, sama seperti metode template? Apakah saya melewatkan sesuatu?
BornToCode

2
@ BornToCode Saya akan berasumsi apa yang mereka bicarakan adalah memilih strategi tertentu pada saat dijalankan. Misalnya, ada beberapa cara menemukan secara numerik akar persamaan. Bergantung pada domain masalah atau data Anda dapat memilih Newton-Raphson, Euler, atau beberapa strategi lain untuk menyelesaikan persamaan. Masing-masing adalah strategi. Algoritma yang lebih besar, yang memecahkan persamaan adalah satu bagian, memilih strategi untuk digunakan berdasarkan beberapa kualitas masalah.
tvanfosson

Ya, tetapi tidak seperti pola strategi yang harus digunakan HANYA untuk kasus-kasus itu? Maksud saya jika saya hanya perlu memilih perilaku algoritma pada waktu kompilasi haruskah saya masih menggunakan pola strategi, atau itu tidak dimaksudkan untuk digunakan seperti itu?
BornToCode

1
@ BornToCode Saya akan mengatakan bahwa strategi paling berguna ketika pilihannya dinamis. Template pada dasarnya adalah cara membangun perilaku yang berbeda dan terkait untuk diketahui. Anda akan menggunakan beberapa strategi (meskipun tidak harus dengan pola strategi) untuk memilih perilaku templated mana yang akan digunakan. Misalnya, pewarisan produk - Anda akan membuat produk dasar, menambahkan fitur untuk berbagai produk. Memilih jenis produk (kelas) mana yang akan dipakai mungkin tergantung pada tabel / tampilan mana itu diambil. Pola strategi tidak benar-benar berperan di sana.
tvanfosson

2
@BornToCode ini bukan salah satu dari / atau itu, itu ya-dan. Terapkan pola yang sesuai, gabungkan pola yang berguna.
tvanfosson


24

Anda mungkin bermaksud pola metode templat. Anda benar, mereka melayani kebutuhan yang sangat mirip. Saya akan mengatakan lebih baik menggunakan metode template dalam kasus ketika Anda memiliki algoritma "template" memiliki langkah-langkah yang ditentukan di mana subclass menimpa langkah-langkah ini untuk mengubah beberapa detail. Dalam hal strategi, Anda perlu membuat antarmuka, dan alih-alih pewarisan yang Anda gunakan delegasi. Saya akan mengatakan itu adalah pola yang sedikit lebih kuat dan mungkin lebih baik sesuai dengan prinsip inversi DIP - dependensi. Ini lebih kuat karena Anda dengan jelas mendefinisikan abstraksi strategi baru - cara melakukan sesuatu, yang tidak berlaku untuk metode templat. Jadi, jika abstraksi ini masuk akal - gunakanlah. Namun, menggunakan metode template dapat memberi Anda desain yang lebih sederhana dalam kasus-kasus sederhana, yang juga penting. Pertimbangkan kata-kata mana yang lebih cocok: apakah Anda memiliki algoritma templat? Atau hal utama di sini adalah Anda memiliki abstraksi strategi - cara baru dalam melakukan sesuatu

Contoh metode templat:

Application.main()
{
Init();
Run();
Done();
}

Di sini Anda mewarisi dari aplikasi dan mengganti apa yang sebenarnya akan dilakukan pada init, jalankan dan selesai.

Contoh strategi:

array.sort (IComparer<T> comparer)

Di sini, saat menulis pembanding, Anda tidak mewarisi dari array. Array mendelegasikan algoritma perbandingan ke pembanding.


3
Saya pikir ini adalah jawaban yang bagus
Calanus

23

Perbedaan antara Strategi dan Metode Templat Pola Strategi vs metode Templat


Kesamaan

Pola strategi dan metode Templat memiliki banyak kesamaan di antara mereka. Pola strategi dan metode Templat dapat digunakan untuk memenuhi Prinsip Terbuka-Tertutup dan membuat modul perangkat lunak mudah diperluas tanpa mengubah kode-kodenya. Kedua pola mewakili pemisahan fungsionalitas generik dari implementasi rinci fungsionalitas itu. Namun, mereka sedikit berbeda dalam hal granularitas yang mereka tawarkan.


Perbedaan

Berikut adalah beberapa perbedaan yang saya amati saat mempelajari dua pola ini:

  1. Dalam Strategi, penggabungan antara klien dan strategi lebih longgar sedangkan dalam Metode Templat, dua modul lebih erat digabungkan.
  2. Dalam Strategi, sebagian besar antarmuka digunakan meskipun kelas abstrak juga dapat digunakan tergantung pada situasi, dan kelas beton tidak digunakan sedangkan dalam metode Templat sebagian besar kelas abstrak atau kelas beton digunakan, antarmuka tidak digunakan.
  3. Dalam pola Strategi, umumnya seluruh perilaku kelas direpresentasikan dalam bentuk antarmuka, di sisi lain, metode Templat digunakan untuk mengurangi duplikasi kode dan kode boilerplate didefinisikan dalam kerangka dasar atau kelas abstrak. Dalam Metode Templat, bahkan bisa ada kelas yang konkret dengan implementasi standar.
  4. Dengan kata-kata sederhana, Anda dapat mengubah seluruh strategi (algoritma) dalam pola Strategi, namun, dalam metode Templat, hanya beberapa hal yang berubah (bagian dari algoritme) dan sisanya tidak berubah. Dalam Metode Templat, langkah-langkah invarian diimplementasikan dalam kelas dasar abstrak, sedangkan langkah-langkah varian diberikan implementasi default, atau tidak ada implementasi sama sekali. Dalam metode Templat, perancang komponen mengamanatkan langkah-langkah yang diperlukan dari suatu algoritma, dan urutan langkah-langkah, tetapi memungkinkan klien komponen untuk memperpanjang atau mengganti sejumlah langkah-langkah ini.

Gambar diambil dari blog yang digigit .


19

Warisan versus agregasi (is-a versus has-a). Ada dua cara untuk mencapai tujuan yang sama.

Pertanyaan ini menunjukkan beberapa trade-off antara pilihan: Warisan vs Agregasi


11

Keduanya sangat mirip, dan keduanya dikonsumsi oleh kode klien dengan cara yang sama. Tidak seperti apa yang dikatakan oleh jawaban paling populer di atas, keduanya memungkinkan pemilihan algoritma saat run-time .

Perbedaan antara keduanya adalah bahwa sementara pola strategi memungkinkan implementasi yang berbeda untuk menggunakan cara yang sama sekali berbeda untuk mencapai hasil yang diinginkan, pola metode templat menentukan algoritma menyeluruh (metode "templat") yang digunakan untuk mencapai hasil - - satu-satunya pilihan yang tersisa untuk implementasi spesifik (sub-kelas) adalah rincian tertentu dari metode templat tersebut. Hal ini dilakukan dengan meminta metode templat melakukan panggilan ke satu atau lebih metode abstrak yang diganti (yaitu diimplementasikan) oleh sub-kelas, tidak seperti metode templat yang dengan sendirinya tidak abstrak dan tidak diganti oleh sub-kelas .

Kode klien melakukan panggilan ke metode templat menggunakan referensi / pointer dari tipe kelas abstrak yang menunjuk ke turunan dari salah satu sub kelas konkret yang dapat ditentukan pada saat run time seperti saat menggunakan Pola Strategi.


9

Metode Templat:

  1. Ini didasarkan pada warisan
  2. Mendefinisikan kerangka algoritma yang tidak dapat diubah oleh sub-kelas. Hanya operasi tertentu yang dapat ditimpa dalam subkelas
  3. Kelas induk sepenuhnya mengontrol algoritma dan hanya berbeda langkah-langkah tertentu untuk kelas konkret
  4. Binding dilakukan pada waktu kompilasi

Struktur template_method :

masukkan deskripsi gambar di sini

Strategi:

  1. Ini didasarkan pada delegasi / komposisi
  2. Ini mengubah isi objek dengan memodifikasi perilaku metode
  3. Ini digunakan untuk beralih di antara keluarga algoritma
  4. Ini mengubah perilaku objek pada saat run time dengan sepenuhnya mengganti satu algoritma dengan algoritma lainnya pada run time
  5. Binding dilakukan saat run time

Struktur strategi :

masukkan deskripsi gambar di sini

Lihatlah metode Templat dan artikel Strategi untuk pemahaman yang lebih baik.

Pos terkait:

Pola desain template di JDK, tidak dapat menemukan metode yang mendefinisikan serangkaian metode yang akan dieksekusi secara berurutan

Contoh Dunia Nyata dari Pola Strategi


3

Tidak, mereka tidak perlu dikonsumsi dengan cara yang sama. Pola "metode templat" adalah cara untuk menyediakan "panduan" bagi pelaksana di masa mendatang. Anda memberi tahu mereka, "Semua objek Orang harus memiliki Nomor Jaminan Sosial" (itu contoh sepele tetapi mendapat ide dengan benar).

Pola strategi memungkinkan beberapa implementasi yang mungkin untuk diaktifkan dan dikeluarkan. Ini tidak (biasanya) dilaksanakan melalui warisan, tetapi sebaliknya dengan membiarkan pemanggil lulus dalam implementasi yang diinginkan. Contoh mungkin memungkinkan ShippingCalculator untuk diberikan dengan salah satu dari beberapa cara berbeda menghitung pajak (implementasi NoSalesTax, dan implementasi PersentaseBasedSalesTax mungkin).

Jadi, kadang-kadang, klien benar-benar akan memberi tahu objek strategi yang digunakan. Seperti dalam

myShippingCalculator.CalculateTaxes(myCaliforniaSalesTaxImpl);

Tetapi klien tidak akan pernah melakukan itu untuk objek yang didasarkan pada Metode Templat. Bahkan, klien mungkin bahkan tidak tahu objek didasarkan pada Metode Templat. Metode-metode abstrak dalam pola Metode Templat bahkan mungkin dilindungi, dalam hal ini klien bahkan tidak akan tahu mereka ada.


3

Saya sarankan Anda untuk membaca artikel ini . Ini menjelaskan perbedaan pada contoh kasus nyata.

Kutipan dari artikel

" Seperti yang dapat dilihat, mengimplementasikan kelas juga bergantung pada kelas metode templat. Ketergantungan ini menyebabkan perubahan metode templat jika seseorang ingin mengubah beberapa langkah algoritma. Di sisi lain, strategi sepenuhnya merangkum algoritma. Ini memberikan implementasi kelas untuk sepenuhnya mendefinisikan suatu algoritma. Oleh karena itu jika ada perubahan yang datang seseorang perlu mengubah kode untuk kelas yang ditulis sebelumnya. Ini adalah alasan utama saya memilih strategi untuk merancang kelas.

Salah satu fitur dari metode template adalah metode template yang mengontrol algoritma. Yang bisa menjadi hal yang baik dalam situasi lain tetapi dalam masalah saya ini membatasi saya untuk merancang kelas. Di sisi lain strategi tidak mengontrol langkah-langkah algoritma yang memungkinkan saya untuk menambahkan metode konversi yang sama sekali berbeda. Karenanya dalam strategi kasus saya membantu saya untuk implementasi.

Salah satu kelemahan strategi adalah terlalu banyak redundansi kode dan lebih sedikit berbagi kode. Seperti yang terlihat jelas dalam contoh artikel ini, saya harus mengulangi kode yang sama dalam empat kelas berulang kali. Oleh karena itu sulit untuk mempertahankan karena jika implementasi sistem kami seperti langkah 4 yang umum untuk semua diubah maka saya harus memperbarui ini di semua 5 kelas. Di sisi lain, dalam metode templat, saya hanya bisa mengubah superclass dan perubahannya tercermin ke dalam sub-kelas. Oleh karena itu metode template memberikan jumlah redundansi yang sangat rendah dan jumlah kode yang tinggi di antara kelas-kelas.

Strategi juga memungkinkan pengubahan algoritma pada saat run-time. Dalam metode templat seseorang harus menginisialisasi ulang objek. Fitur strategi ini menyediakan sejumlah besar fleksibilitas. Dari sudut pandang desain, kita harus lebih suka komposisi daripada warisan. Oleh karena itu menggunakan pola strategi juga menjadi pilihan utama untuk pengembangan. "


2

Pola Templat mirip dengan pola Strategi. Kedua pola ini berbeda dalam ruang lingkup dan metodologi.

Strategi digunakan untuk memungkinkan penelepon memvariasikan seluruh algoritme, seperti cara menghitung berbagai jenis pajak, sedangkan Metode Templat digunakan untuk memvariasikan langkah-langkah dalam suatu algoritma. Karena itu, Strategi lebih berbutir kasar. Templat memungkinkan kontrol berbutir halus dalam urutan operasi, namun memungkinkan implementasi rincian ini bervariasi.

Perbedaan utama lainnya adalah bahwa Strategi menggunakan pendelegasian sementara Metode Templat menggunakan pewarisan. Dalam Strategi, algoritme didelegasikan ke kelas xxxStrategy lain yang akan dijadikan referensi oleh subjek, tetapi dengan Templat Anda mensubklasifikasikan basis dan mengganti metode untuk membuat perubahan.

dari http://cyruscrypt.blogspot.com/2005/07/template-vs-strategy-patterns.html


2

Dalam subclass pola strategi menjalankan pertunjukan dan mereka mengontrol algoritma. Di sini kode digandakan di seluruh subclass. Pengetahuan tentang algoritma dan cara mengimplementasikannya didistribusikan ke banyak kelas.

Dalam pola templat, kelas dasar memiliki algoritma. Ini memaksimalkan penggunaan kembali di antara subclass. Karena algoritma terletak di satu tempat, kelas dasar melindunginya.


2

Pola Desain Strategi

  • Mendukung komposisi.
  • Memberi Anda fleksibilitas untuk mengubah perilaku objek saat runtime.
  • Kurang kopling antara kode klien dan kode solusi / algoritma.

Metode Templat Pola Desain

  • Lebih menyukai warisan daripada komposisi
  • Tetapkan algoritma di kelas dasar Anda. Masing-masing potongan algoritma dapat dikustomisasi di kelas anak.

1

Pola Templat:

Metode template adalah tentang membiarkan subclass mendefinisikan kembali langkah-langkah tertentu dari algoritma, tanpa mengubah struktur utama dan langkah-langkah algoritma, yang didefinisikan dalam kelas dasar. Pola templat biasanya menggunakan pewarisan, sehingga implementasi umum dari algoritma dapat disediakan di kelas dasar, yang dapat ditimpa oleh subkelas jika perlu.

public abstract class RobotTemplate {
    /* This method can be overridden by a subclass if required */
    public void start() {
        System.out.println("Starting....");
    }

    /* This method can be overridden by a subclass if required */
    public void getParts() {
        System.out.println("Getting parts....");
    }

    /* This method can be overridden by a subclass if required */
    public void assemble() {
        System.out.println("Assembling....");
    }

    /* This method can be overridden by a subclass if required */
    public void test() {
        System.out.println("Testing....");
    }

    /* This method can be overridden by a subclass if required */
    public void stop() {
        System.out.println("Stopping....");
    }

    /*
     * Template algorithm method made up of multiple steps, whose structure and
     * order of steps will not be changed by subclasses.
     */
    public final void go() {
        start();
        getParts();
        assemble();
        test();
        stop();
    }
}


/* Concrete subclass overrides template step methods as required for its use */
public class CookieRobot extends RobotTemplate {
    private String name;

    public CookieRobot(String n) {
        name = n;
    }

    @Override
    public void getParts() {
        System.out.println("Getting a flour and sugar....");
    }

    @Override
    public void assemble() {
        System.out.println("Baking a cookie....");
    }

    @Override
    public void test() {
        System.out.println("Crunching a cookie....");
    }

    public String getName() {
        return name;
    }
}

Catatan dalam kode di atas, langkah-langkah algoritma go () akan selalu sama, tetapi subclass mungkin mendefinisikan resep berbeda untuk melakukan langkah tertentu.

Pola Strategi:

Pola strategi adalah tentang membiarkan klien memilih implementasi algoritma konkret saat runtime. Semua algoritma terisolasi dan independen, tetapi menerapkan antarmuka umum, dan tidak ada gagasan untuk menentukan langkah-langkah tertentu dalam algoritma.

/**
 * This Strategy interface is implemented by all concrete objects representing an
 * algorithm(strategy), which lets us define a family of algorithms.
 */
public interface Logging {
    void write(String message);
}

/**
 * Concrete strategy class representing a particular algorithm.
 */
public class ConsoleLogging implements Logging {

    @Override
    public void write(String message) {
        System.out.println(message); 
    }

}

/**
 * Concrete strategy class representing a particular algorithm.
 */
public class FileLogging implements Logging {

    private final File toWrite;

    public FileLogging(final File toWrite) {
        this.toWrite = toWrite;
    }

    @Override
    public void write(String message) {
        try {
            final FileWriter fos = new FileWriter(toWrite);
            fos.write(message);
            fos.close();
        } catch (IOException e) {
            System.out.println(e);
        }
    }

}

Untuk kode sumber lengkap, lihat repositori github saya .


0

Strategi diekspos sebagai metode Antarmuka dan templat sebagai Kelas Abstrak. Ini biasanya banyak digunakan dalam kerangka kerja. misalnya kelas MessageSource Spring framework adalah antarmuka strategi untuk menyelesaikan pesan. Klien menggunakan implementasi (strategi) tertentu dari antarmuka ini.

Dan implementasi abstrak dari interface yang sama AbstractMessageSource, yang memiliki implementasi umum dari penyelesaian pesan dan memperlihatkan metode abstrak resolCode () sehingga sub-kelas dapat mengimplementasikannya dengan cara mereka. AbstractMessageSource adalah contoh metode templat.

http://docs.spring.io/spring/docs/4.1.7.RELEASE/javadoc-api/org/springframework/context/support/AbstractMessageSource.html


0

Dalam metode templat pola desain ini, satu atau beberapa langkah algoritma dapat ditimpa oleh subkelas untuk memungkinkan perilaku yang berbeda sambil memastikan bahwa algoritma menyeluruh masih diikuti (Wiki).

Metode pola nama Templat berarti apa itu. Katakanlah kita memiliki metode CalculateSomething () dan kami ingin membuat templat metode ini. Metode ini akan dinyatakan dalam kelas dasar sebagai metode non virtual. Katakanlah metodenya terlihat seperti ini.

CalculateSomething(){
    int i = 0;
    i = Step1(i);
    i++;
    if (i> 10) i = 5;
    i = Step2(i);
    return i;

} Implementasi metode Step1 dan Step2 dapat diberikan oleh kelas turunan.

Dalam Pola Strategi tidak ada implementasi yang disediakan oleh basis (Ini adalah alasan mengapa basis benar-benar sebuah antarmuka dalam diagram kelas)

Contoh klasiknya adalah sortir. Berdasarkan jumlah objek yang perlu disortir, kelas algoritma yang sesuai (gabungan, gelembung, dll.) Dibuat dan seluruh algoritma dienkapsulasi di setiap kelas.

Sekarang bisakah kita menerapkan pengurutan sebagai metode templat? Tentu saja Anda bisa, tetapi Anda tidak akan menemukan banyak kesamaan untuk disarikan dan ditempatkan dalam implementasi basis. Jadi itu mengalahkan tujuan pola metode templat.

Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.