পদ্ধতি শৃঙ্খলা ব্যবহার করার সময়, আমি কি বস্তুটি পুনরায় ব্যবহার করব বা একটি তৈরি করব?


37

পদ্ধতিতে শৃঙ্খলা ব্যবহার করার সময়:

var car = new Car().OfBrand(Brand.Ford).OfModel(12345).PaintedIn(Color.Silver).Create();

দুটি উপায় থাকতে পারে:

  • একই জিনিসটি পুনরায় ব্যবহার করুন:

    public Car PaintedIn(Color color)
    {
        this.Color = color;
        return this;
    }
    
  • Carপ্রতিটি পদক্ষেপে টাইপের একটি নতুন অবজেক্ট তৈরি করুন :

    public Car PaintedIn(Color color)
    {
        var car = new Car(this); // Clone the current object.
        car.Color = color; // Assign the values to the clone, not the original object.
        return car;
    }
    

প্রথমটি কি ভুল বা এটি বরং বিকাশকারীর ব্যক্তিগত পছন্দ?


আমি বিশ্বাস করি যে তিনি প্রথম যোগাযোগের কারণে দ্রুত স্বজ্ঞাত / বিভ্রান্তিকর কোড তৈরি করতে পারে। উদাহরণ:

// Create a car with neither color, nor model.
var mercedes = new Car().OfBrand(Brand.MercedesBenz).PaintedIn(NeutralColor);

// Create several cars based on the neutral car.
var yellowCar = mercedes.PaintedIn(Color.Yellow).Create();
var specificModel = mercedes.OfModel(99).Create();

// Would `specificModel` car be yellow or of neutral color? How would you guess that if
// `yellowCar` were in a separate method called somewhere else in code?

কোন চিন্তা?


1
এর সাথে কী হয়েছে var car = new Car(Brand.Ford, 12345, Color.Silver);?
জেমস

12
@ জামেস টেলিস্কোপিক কনস্ট্রাক্টর, সাবলীল প্যাটার্নটি alচ্ছিক এবং প্রয়োজনীয় প্যারামিটারগুলির মধ্যে পার্থক্য করতে সহায়তা করতে পারে (যদি তারা কনস্ট্রাক্টর আর্গগুলির প্রয়োজন হয় তবে optionচ্ছিক না হলে)। এবং সাবলীল পড়া বরং সুন্দর।
নিমচিম্পস্কি

8
@ নিমচিম্পস্কি ভাল পুরানো ধাঁচের (সি # এর জন্য) বৈশিষ্ট্যগুলির জন্য কী ঘটেছে, এবং এমন একটি নির্মাণকারীর ক্ষেত্র রয়েছে যা প্রয়োজনীয় - এটি নয় যে আমি ফ্লুয়েন্ট এপিআইগুলিকে ব্লাস্ট করছি, আমি একটি বড় অনুরাগী কিন্তু তারা প্রায়শই অপব্যবহার করছি
ক্রিস এস

8
@ ক্রিসস যদি আপনি সেটারের উপর নির্ভর করেন (আমি জাভা থেকে এসেছি) আপনাকে আপনার অবজেক্টগুলিকে পরিবর্তন করতে হবে, যা আপনি নাও করতে চান। এবং সাবলীল ব্যবহার করার সময় আপনি আরও সুন্দর ইন্টেলাইটেক্সট পান - কম চিন্তাভাবনার প্রয়োজন, আদর্শটি আপনার জন্য আপনার বস্তুটি প্রায় তৈরি করে।
নিমচিম্পস্কি

1
@ নিমচিম্পস্কি ইয়ে আমি দেখতে পাচ্ছি যে জাভার পক্ষে কতটা সাবলীলভাবে এগিয়ে যেতে পারে
ক্রিস এস

উত্তর:


41

আমি সাবলীল এপিটিকে এটির নিজের "বিল্ডার" শ্রেণীর দ্বারা তৈরি করা বস্তু থেকে পৃথক করে রাখতে চাই। এইভাবে, ক্লায়েন্ট যদি সাবলীল এপিআই ব্যবহার করতে না চান তবে আপনি এটি নিজে নিজে ব্যবহার করতে পারেন এবং এটি ডোমেন অবজেক্টকে দূষিত করে না (একক দায়িত্বের নীতিটি মেনে চলা)। এক্ষেত্রে নিম্নলিখিতটি তৈরি করা হবে:

  • Car যা ডোমেন অবজেক্ট
  • CarBuilder যা সাবলীল এপিআই রাখে

ব্যবহারটি এরকম হবে:

var car = CarBuilder.BuildCar()
    .OfBrand(Brand.Ford)
    .OfModel(12345)
    .PaintedIn(Color.Silver)
    .Build();

CarBuilderবর্গ (আমি সি # নামকরণ কনভেনশন এখানে ব্যবহার করছি) এই মত দেখাবে:

public class CarBuilder {

    private Car _car;

    /// Constructor
    public CarBuilder() {
        _car = new Car();
        SetDefaults();
    }

    private void SetDefaults() {
        this.OfBrand(Brand.Ford);
          // you can continue the chaining for 
          // other default values
    }

    /// Starts an instance of the car builder to 
    /// build a new car with default values.
    public static CarBuilder BuildCar() {
        return new CarBuilder();
    }

    /// Sets the brand
    public CarBuilder OfBrand(Brand brand) {
        _car.SetBrand(brand);
        return this;
    }

    // continue with OfModel(...), PaintedIn(...), and so on...
    // that returns "this" to allow method chaining

    /// Returns the built car
    public Car Build() {
        return _car;
    }

}

মনে রাখবেন যে এই শ্রেণিটি থ্রেড নিরাপদে থাকবে না (প্রতিটি থ্রেডের নিজস্ব কারবিল্ডার উদাহরণ প্রয়োজন)। আরও মনে রাখবেন যে সাবলীল এপিআই সত্যিই দুর্দান্ত ধারণা হলেও এটি সাধারণ ডোমেন অবজেক্ট তৈরি করার উদ্দেশ্যে সম্ভবত ওভারকিল।

আপনি যদি আরও বিমূর্ত কিছুর জন্য একটি এপিআই তৈরি করে থাকেন এবং আরও জটিল সেটআপ এবং কার্যকর করেন তবে এই ইউনিট পরীক্ষা এবং ডিআই ফ্রেমওয়ার্কগুলিতে দুর্দান্ত কাজ করে যদি এই চুক্তিটি আরও কার্যকর। আপনি উইকিপিডিয়া ফ্লুয়েন্ট ইন্টারফেস নিবন্ধের জাভা বিভাগের অধীনে আরও কিছু উদাহরণ দেখতে পারেন যা দৃistance়তা, তারিখ পরিচালনা এবং মক অবজেক্টের সাথে রয়েছে।


সম্পাদনা করুন:

মতামত থেকে উল্লিখিত; আপনি বিল্ডার শ্রেণিটিকে একটি স্থিতিশীল অভ্যন্তর শ্রেণি তৈরি করতে পারেন (গাড়ির অভ্যন্তরে) এবং গাড়িটি অনাবৃত হতে পারে। গাড়িটিকে অপরিবর্তনীয় হতে দেওয়ার এই উদাহরণটি কিছুটা নির্বোধ বলে মনে হচ্ছে; তবে আরও জটিল সিস্টেমে যেখানে আপনি নির্মিত অবজেক্টের সামগ্রীগুলি একেবারে পরিবর্তন করতে চান না, আপনি এটি করতে চাইবেন want

স্থিতিশীল অভ্যন্তর শ্রেণি উভয়ই কীভাবে করা যায় এবং কীভাবে এটি অপসারণযোগ্য অবজেক্ট তৈরি করতে পারে তা হ'ল তার উদাহরণ নীচে:

// the class that represents the immutable object
public class ImmutableWriter {

    // immutable variables
    private int _times; private string _write;

    // the "complex" constructor
    public ImmutableWriter(int times, string write) {
        _times = times;
        _write = write;
    }

    public void Perform() {
        for (int i = 0; i < _times; i++) Console.Write(_write + " ");
    }

    // static inner builder of the immutable object
    protected static class ImmutableWriterBuilder {

        // the variables needed to construct the immutable object
        private int _ii = 0; private string _is = String.Empty;

        public void Times(int i) { _ii = i; }

        public void Write(string s) { _is = s; }

        // The stuff is all built here
        public ImmutableWriter Build() {
            return new ImmutableWriter(_ii, _is);
        }

    }

    // factory method to get the builder
    public static ImmutableWriterBuilder GetBuilder() {
        return new ImmutableWriterBuilder();
    }
}

ব্যবহার নিম্নলিখিত হবে:

var writer = ImmutableWriter
                .GetBuilder()
                .Write("peanut butter jelly time")
                .Times(2)
                .Build();

writer.Perform();
// console writes: peanut butter jelly time peanut butter jelly time 

সম্পাদনা 2: মন্তব্যগুলিতে পিট জটিল ডোমেন অবজেক্টের সাথে ইউনিট টেস্ট লেখার প্রসঙ্গে ল্যাম্বডা ফাংশন সহ বিল্ডারদের ব্যবহার সম্পর্কে ব্লগ পোস্ট করেছে । বিল্ডারটিকে আরও কিছুটা ভাবপূর্ণ করার জন্য এটি একটি আকর্ষণীয় বিকল্প।

ক্ষেত্রে CarBuilderআপনি যদি এর পরিবর্তে এই পদ্ধতি আছে প্রয়োজন:

public static Car Build(Action<CarBuilder> buildAction = null) {
    var carBuilder = new CarBuilder();
    if (buildAction != null) buildAction(carBuilder);
    return carBuilder._car;
}

যা এটি হিসাবে ব্যবহার করা যেতে পারে:

Car c = CarBuilder
    .Build(car => 
        car.OfBrand(Brand.Ford)
           .OfModel(12345)
           .PaintedIn(Color.Silver);

3
@ বাউকেটাকে এটি জোশ
ব্লচের

6
@ বাউকেটার জন্য জাভা দেব, ইমো পড়ার প্রয়োজন।
নিমচিম্পস্কি

3
আইএমএইচও-র একটি বিশাল সুবিধা হ'ল, আপনি নির্মাত্ত্রের অবতরণ থেকে শেষ না হওয়া নির্মাণাধীন অবজেক্টের দৃষ্টান্তগুলি রোধ করতে এই প্যাটার্নটি (যথাযথভাবে সংশোধন করা হলে) ব্যবহার করতে পারেন। উদাহরণস্বরূপ আপনি নিশ্চিত করতে পারেন যে কোনও অপরিবর্তিত রঙের কোনও গাড়ি থাকবে না।
স্কার্ফ্রিজে

1
হুম ... আমি সর্বদা বিল্ডার প্যাটার্নের চূড়ান্ত পদ্ধতিটি build()(বা Build()) ডেকেছি , এটি যে ধরণের নির্মাণ করে তার নাম নয় ( Car()আপনার উদাহরণে)। এছাড়াও, যদি Carসত্যই অপরিবর্তনীয় বস্তু হয় (যেমন, এর সমস্ত ক্ষেত্রগুলি readonly), তবে এমনকি নির্মাতাও এটিকে রূপান্তর করতে সক্ষম হবেন না, সুতরাং Build()পদ্ধতিটি নতুন উদাহরণটি নির্মাণের জন্য দায়ী হয়ে যায়। এটি করার একটি উপায় হ'ল Carকেবলমাত্র একক কনস্ট্রাক্টর থাকবেন, যা একজন নির্মাতাকে তার যুক্তি হিসাবে গ্রহণ করে; তারপর Build()পদ্ধতি মাত্র পারেন return new Car(this);
ড্যানিয়েল প্রাইডেন 25'12

1
ল্যাম্বডাসের উপর ভিত্তি করে বিল্ডার তৈরির বিষয়ে আমি একটি ভিন্ন পদ্ধতির বিষয়ে ব্লগ করেছি। পোস্টটির সম্ভবত কিছুটা সম্পাদনা দরকার। আমার প্রসঙ্গটি বেশিরভাগই ইউনিট পরীক্ষার সুযোগের ভিতরে ছিল তবে এটি অন্যান্য ক্ষেত্রেও প্রয়োগ করা যেতে পারে যদি প্রযোজ্য হয়। এটি এখানে পাওয়া যাবে: petesdotnet.blogspot.com/2012/05/…
পিট

9

যা নির্ভর করে.

আপনার গাড়ী একটি সত্তা বা একটি মান অবজেক্ট ? গাড়ী যদি কোনও সত্তা হয় তবে অবজেক্টের পরিচয়টি গুরুত্ব দেয়, সুতরাং আপনার একই রেফারেন্সটি ফিরিয়ে দেওয়া উচিত। যদি বস্তুটি একটি মান অবজেক্ট হয় তবে তা পরিবর্তনযোগ্য হওয়া উচিত, যার অর্থ একমাত্র উপায় প্রতিবার একটি নতুন উদাহরণ ফিরে আসা।

পরেরটির একটি উদাহরণ .NET এ ডেটটাইম ক্লাস হবে যা একটি মান বস্তু।

var date1 = new DateTime(2012,1,1);
var date2 = date1.AddDays(1);
// date2 now refers to Jan 2., while date1 remains unchanged at Jan 1.

তবে যদি মডেলটি কোনও সত্তা হয় তবে আমি আপনাকে আপত্তি জানাতে কোনও বিল্ডার শ্রেণি ব্যবহারের বিষয়ে স্পোকের উত্তরটি পছন্দ করি। অন্য কথায়, যে উদাহরণটি আপনি দিয়েছেন তা কেবল আইএমএইচও-র জন্য বোধগম্য হয় যদি গাড়ী একটি মূল্যমানের অবজেক্ট হয়।


1
'সত্তা' বনাম 'মান' প্রশ্নের জন্য +1। এটি একটি প্রশ্ন যা আপনার শ্রেণিটি কোনও পরিবর্তনীয় বা অপরিবর্তনীয় ধরণের (এই বিষয়টিকে কী পরিবর্তন করা উচিত?) এবং সম্পূর্ণ আপনার উপর নির্ভর করে, যদিও এটি আপনার নকশাকে প্রভাবিত করবে। পদ্ধতিটি কোনও নতুন অবজেক্ট ফেরত না দিলে আমি সাধারণত পরিবর্তনের ধরণের পদ্ধতিতে শৃঙ্খলাবদ্ধতার আশা করব না।
কেসি কুবাল

6

একটি পৃথক স্থিতিশীল অভ্যন্তর নির্মাতা তৈরি করুন।

প্রয়োজনীয় পরামিতিগুলির জন্য সাধারণ কনস্ট্রাক্টর আর্গুমেন্ট ব্যবহার করুন। এবং fluচ্ছিক জন্য সাবলীল এপিআই।

রঙ সেট করার সময় কোনও নতুন অবজেক্ট তৈরি করবেন না, যদি না আপনি পদ্ধতিটির নতুন নামকরণ করেন NewCarInColour বা অনুরূপ কিছু।

আমি যেমন প্রয়োজন তেমন ব্র্যান্ড না দিয়ে এইরকম কিছু করব এবং বাকী alচ্ছিক (এটি জাভা, তবে আপনার জাভাস্ক্রিপ্টের মতো দেখাচ্ছে তবে তারা নিশ্চিত যে কিছুটা নিট বাছাইয়ের সাথে তারা বিনিময়যোগ্য):

Car yellowMercedes = new Car.Builder(Brand.MercedesBenz).PaintedIn(Color.Yellow).create();

Car specificYellowModel =new Car.Builder(Brand.MercedesBenz).WithModel(99).PaintedIn(Color.Yellow).create();

4

সর্বাধিক গুরুত্বপূর্ণ বিষয়টি হ'ল আপনি যে সিদ্ধান্তটিই বেছে নিন তা পদ্ধতির নাম এবং / অথবা মন্তব্যে স্পষ্টভাবে বলা আছে।

কোনও মান নেই, কখনও কখনও পদ্ধতিটি একটি নতুন অবজেক্ট ফিরিয়ে দেয় (বেশিরভাগ স্ট্রিং পদ্ধতি এটি করে) বা শৃঙ্খলাবদ্ধ উদ্দেশ্যে বা মেমরির দক্ষতার জন্য এই বস্তুটি ফিরিয়ে দেবে)।

আমি একবার একটি 3 ডি ভেক্টর অবজেক্ট ডিজাইন করেছি এবং প্রতিটি গণিত ক্রিয়াকলাপের জন্য আমার উভয় পদ্ধতিই প্রয়োগ করা হয়েছিল। তাত্ক্ষণিক স্কেল পদ্ধতির জন্য:

Vector3D scaleLocal(float factor){
    this.x *= factor; 
    this.y *= factor; 
    this.z *= factor; 
    return this;
}

Vector3D scale(float factor){
    Vector3D that = new Vector3D(this); // clone this vector
    return that.scaleLocal(factor);
}

3
+1 টি। খুব ভাল পয়েন্ট। কেন এটি ডাউনটা পেয়েছে তা আমি সত্যিই দেখছি না। তবে আমি খেয়াল করব, আপনি যে নামগুলি পছন্দ করেছেন তা খুব পরিষ্কার নয়। আমি তাদের scale(মিউটর) এবং scaledBy(জেনারেটর) কল করব ।
back2dos

ভালো কথা, নামগুলি আরও পরিষ্কার হতে পারে। নামকরণটি একটি গ্রন্থাগার থেকে আমি ব্যবহৃত অন্যান্য গাণিতিক ক্লাসগুলির একটি সম্মেলন অনুসরণ করে। প্রভাবটি বিভ্রান্তি এড়াতে পদ্ধতির জাভাদোক মন্তব্যেও বলা হয়েছিল।
এক্সগাউচেট

3

আমি এখানে কয়েকটি সমস্যা দেখছি যা আমার মনে হয় বিভ্রান্তিকর হতে পারে ... আপনার প্রশ্নের প্রথম লাইনটি:

var car = new Car().OfBrand(Brand.Ford).OfModel(12345).PaintedIn(Color.Silver).Create();

আপনি একটি কনস্ট্রাক্টর (নতুন) এবং একটি তৈরি পদ্ধতি কল করছেন ... একটি তৈরি () পদ্ধতি প্রায় সর্বদা স্থির পদ্ধতি বা নির্মাতা পদ্ধতি হতে পারে, এবং সংকলকটি আপনাকে জানাতে কোনও সতর্কতা বা ত্রুটিতে ধরা উচিত, হয় হয় উপায়, এই বাক্য গঠনটি ভুল বা কিছু ভয়ানক নাম রয়েছে। তবে পরে, আপনি উভয় ব্যবহার করবেন না, সুতরাং আসুন এটি তাকান।

// Create a car with neither color, nor model.
var mercedes = new Car().OfBrand(Brand.MercedesBenz).PaintedIn(NeutralColor);

// Create several cars based on the neutral car.
var yellowCar = mercedes.PaintedIn(Color.Yellow).Create();
var specificModel = mercedes.OfModel(99).Create();

আবার তৈরি করার পরেও, কেবল নতুন নির্মাণকারীর সাথে নয়। কথাটি হ'ল, আমি মনে করি আপনি পরিবর্তে একটি অনুলিপি () পদ্ধতি সন্ধান করছেন। সুতরাং যদি এটি হয়, এবং এটি কেবল একটি খারাপ নাম, আসুন একটি জিনিস দেখুন ... আপনি মার্সেডিজকে কল করুন ain পেইন্টিনকে (কালার.ইয়েলো) op কপি () - এটি দেখার পক্ষে এটি সহজে বলা উচিত যে এটি আঁকা হচ্ছে tell 'অনুলিপি করার আগে - আমার কাছে যুক্তির একটি সাধারণ প্রবাহ। কপিটি আগে রাখুন।

var yellowCar = mercedes.Copy().PaintedIn(Color.Yellow)

আমার কাছে, এটি সহজেই দেখতে পারা যায় যে আপনি অনুলিপিটি আঁকছেন, আপনার হলুদ গাড়ি তৈরি করছেন।


নতুন এবং তৈরি () এর মধ্যে বিভেদ দেখানোর জন্য +1;
জোশুয়া ড্রেক

1

প্রথম পদ্ধতির আপনার উল্লেখযোগ্য ত্রুটি রয়েছে তবে আপনি যতক্ষণ ডক্সে এটি স্পষ্ট করে দেবেন কোনও অর্ধ-সক্ষম কোডার যাতে সমস্যা না থাকে। আমি ব্যক্তিগতভাবে যে পদ্ধতি-চেইন কোডটির সাথে কাজ করেছি সেগুলির সমস্তই এইভাবে কাজ করেছে।

দ্বিতীয় পদ্ধতির স্পষ্টতই আরও বেশি কাজ হওয়ার ত্রুটি রয়েছে। আপনার ফিরে আসা অনুলিপিগুলি অগভীর বা গভীর অনুলিপিগুলি যাচ্ছেন কিনা তাও আপনাকে স্থির করতে হবে: যা সর্বোপরি শ্রেণি থেকে অন্য পদ্ধতিতে বা পদ্ধতিতে ভিন্ন হতে পারে, তাই আপনি হয় অসঙ্গতি প্রবর্তন করবেন বা সেরা আচরণের সাথে আপস করবেন। এটি লক্ষণীয় যে এটি স্ট্রিংয়ের মতো অপরিবর্তনীয় বস্তুর জন্য একমাত্র বিকল্প।

আপনি যা করেন না কেন, একই ক্লাসের মধ্যে মেশা এবং মিলবেন না!


1

আমি বরং "এক্সটেনশন পদ্ধতি" পদ্ধতির মতোই ভাবি।

public Car PaintedIn(this Car car, Color color)
{
    car.Color = color;
    return car;
}

0

এটি উপরের পদ্ধতিগুলির মধ্যে একটি প্রকরণ। পার্থক্যগুলি হ'ল কার শ্রেণিতে স্থিতিশীল পদ্ধতি রয়েছে যা বিল্ডারের পদ্ধতির নামের সাথে মেলে, সুতরাং আপনার স্পষ্টভাবে কোনও বিল্ডার তৈরি করার দরকার নেই:

Car car = Car.builder().ofBrand(Brand.Ford).ofColor("Green")...

শৃঙ্খলবদ্ধ নির্মাতাদের কলগুলিতে আপনি একই পদ্ধতির নামগুলি ব্যবহার করতে পারেন:

Car car = Car.ofBrand(Brand.Ford).ofColor("Green")...

এছাড়াও, ক্লাসে একটি .copy () পদ্ধতি রয়েছে যা বর্তমান উদাহরণ থেকে সমস্ত মান সহ একটি নির্মাতাকে ফেরত দেয়, তাই আপনি কোনও থিমের উপর একটি পরিবর্তন তৈরি করতে পারেন:

Car red = car.copy().paintedIn("Red").build();

অবশেষে, বিল্ডারের। বিল্ড () পদ্ধতিটি পরীক্ষা করে যে সমস্ত প্রয়োজনীয় মান সরবরাহ করা হয়েছে এবং যদি কোনও অনুপস্থিত থাকে তবে তা ছুড়ে দেয়। নির্মাতার কনস্ট্রাক্টরের কিছু মান প্রয়োজন এবং বাকীটি alচ্ছিকভাবে থাকতে দেওয়া আরও ভাল; সেক্ষেত্রে আপনি অন্য উত্তরের একটি নিদর্শন চান।

public enum Brand {
    Ford, Chrysler, GM, Honda, Toyota, Mercedes, BMW, Lexis, Tesla;
}

public class Car {
    private final Brand brand;
    private final int model;
    private final String color;

    public Car(Brand brand, int model, String color) {
        this.brand = brand;
        this.model = model;
        this.color = color;
    }

    public Brand getBrand() {
        return brand;
    }

    public int getModel() {
        return model;
    }

    public String getColor() {
        return color;
    }

    @Override public String toString() {
        return brand + " " + model + " " + color;
    }

    public Builder copy() {
        Builder builder = new Builder();
        builder.brand = brand;
        builder.model = model;
        builder.color = color;
        return builder;
    }

    public static Builder ofBrand(Brand brand) {
        Builder builder = new Builder();
        builder.brand = brand;
        return builder;
    }

    public static Builder ofModel(int model) {
        Builder builder = new Builder();
        builder.model = model;
        return builder;
    }

    public static Builder paintedIn(String color) {
        Builder builder = new Builder();
        builder.color = color;
        return builder;
    }

    public static class Builder {
        private Brand brand = null;
        private Integer model = null;
        private String color = null;

        public Builder ofBrand(Brand brand) {
            this.brand = brand;
            return this;
        }

        public Builder ofModel(int model) {
            this.model = model;
            return this;
        }

        public Builder paintedIn(String color) {
            this.color = color;
            return this;
        }

        public Car build() {
            if (brand == null) throw new IllegalArgumentException("no brand");
            if (model == null) throw new IllegalArgumentException("no model");
            if (color == null) throw new IllegalArgumentException("no color");
            return new Car(brand, model, color);
        }
    }
}
আমাদের সাইট ব্যবহার করে, আপনি স্বীকার করেছেন যে আপনি আমাদের কুকি নীতি এবং গোপনীয়তা নীতিটি পড়েছেন এবং বুঝতে পেরেছেন ।
Licensed under cc by-sa 3.0 with attribution required.