কোটলিন ডেটা ক্লাসের জন্য ওভাররাইড গেটার


99

নিম্নলিখিত কোটলিন শ্রেণি দেওয়া:

data class Test(val value: Int)

Intমানটি নেতিবাচক হলে আমি কীভাবে গেটকে ওভাররাইড করব যাতে এটি 0 ফেরত দেয়?

যদি এটি সম্ভব না হয় তবে উপযুক্ত ফলাফল অর্জনের জন্য কিছু কৌশল কী?


14
অনুগ্রহ করে আপনার কোডের কাঠামোটি পরিবর্তন করে বিবেচনা করুন যাতে ক্লাসটি ইনস্ট্যান্ট করার সময় নেতিবাচক মানগুলি 0 তে রূপান্তরিত হয়, এবং কোনও গেটরে থাকে না। আপনি যদি নীচের উত্তরে বর্ণিত গেটকে ওভাররাইড করে থাকেন তবে অন্যান্য উত্পন্ন সমস্ত পদ্ধতি যেমন সমান (), টোস্ট্রিং () এবং উপাদান অ্যাক্সেস এখনও মূল নেতিবাচক মান ব্যবহার করবে, যা সম্ভবত অবাক করা আচরণের দিকে পরিচালিত করবে।
ইয়োল

উত্তর:


148

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

  1. আপনার ব্যবসায়ের যুক্তি আছে যা data classখারাপ মান দিয়ে কনস্ট্রাক্টরকে কল করার আগে 0 বা তার চেয়ে বড় মান পরিবর্তন করে creates এটি বেশিরভাগ ক্ষেত্রে সম্ভবত সেরা পন্থা।

  2. একটি ব্যবহার করবেন না data class। একটি নিয়মিত ব্যবহার করুন classএবং আপনার আইডিই আপনার জন্য পদ্ধতি equalsএবং hashCodeপদ্ধতিগুলি তৈরি করুন (বা না, যদি আপনার প্রয়োজন না হয়)। হ্যাঁ, কোনও বস্তুতে কোনও বৈশিষ্ট্য পরিবর্তিত হলে আপনাকে এটি পুনরায় তৈরি করতে হবে, তবে আপনি অবজেক্টের সম্পূর্ণ নিয়ন্ত্রণে রেখে গেছেন।

    class Test(value: Int) {
      val value: Int = value
        get() = if (field < 0) 0 else field
    
      override fun equals(other: Any?): Boolean {
        if (this === other) return true
        if (other !is Test) return false
        return true
      }
    
      override fun hashCode(): Int {
        return javaClass.hashCode()
      }
    }
    
  3. অবজেক্টে অতিরিক্ত সুরক্ষিত সম্পত্তি তৈরি করুন যা কার্যকরভাবে ওভাররেড করা ব্যক্তিগত মূল্য থাকার পরিবর্তে আপনি যা চান তা করেন।

    data class Test(val value: Int) {
      val safeValue: Int
        get() = if (value < 0) 0 else value
    }
    

একটি খারাপ দৃষ্টিভঙ্গি যা অন্যান্য উত্তরগুলি পরামর্শ দিচ্ছে:

data class Test(private val _value: Int) {
  val value: Int
    get() = if (_value < 0) 0 else _value
}

এই পদ্ধতির সমস্যাটি হ'ল ডেটা ক্লাসগুলি আসলে এই জাতীয় ডেটা পরিবর্তনের জন্য বোঝানো হয় না। তারা সত্যিই কেবল তথ্য ধরে রাখার জন্য। এই মত একটি তথ্য বর্গ জন্য সংগ্রহকারী উপেক্ষা করে বোঝাতে চায় Test(0)এবং Test(-1)would না equalপরস্পর এবং বিভিন্ন থাকবে hashCodeগুলি, কিন্তু তোমাকে ডেকেছিলাম .value, তারা একই ফলাফল হবে। এটি অসঙ্গতিপূর্ণ এবং এটি আপনার পক্ষে কার্যকর হতে পারে, আপনার দলের অন্যান্য লোকেরা যারা এটি একটি ডেটা শ্রেণি দেখছেন, আপনি কীভাবে এটি পরিবর্তন করেছেন / এটি প্রত্যাশিতভাবে কাজ না করেছেন তা উপলব্ধি না করে দুর্ঘটনাক্রমে এটির অপব্যবহার করতে পারে (অর্থাত্ এই পদ্ধতির ইচ্ছা হবে না) টি একটি Mapবা এ Set) তে সঠিকভাবে কাজ করে ।


সিরিয়ালাইজেশন / ডিসিরিয়ালাইজেশন, নেস্টেড কাঠামোর সমতলকরণের জন্য ব্যবহৃত ডেটা ক্লাসগুলি সম্পর্কে কী বলা যায়? উদাহরণস্বরূপ, আমি কেবল লিখেছি data class class(@JsonProperty("iss_position") private val position: Map<String, Double>) { val latitude = position["latitude"]; val longitude = position["longitude"] }, এবং আমি এটি আমার ক্ষেত্রে টিবিএইচ জন্য বেশ ভাল মনে করি। আপনি এ ব্যপারে কী ভাবছেন? (অন্যান্য ক্ষেত্রগুলির মধ্যে অনেকগুলি ছিল এবং তাই আমি বিশ্বাস করি যে আমার কোডে নেস্টেড
জসন

@ অ্যানটেক আপনি এই তথ্যটি পরিবর্তন করছেন না তা প্রদত্ত, এই পদ্ধতির সাথে আমি কোনও ভুল দেখছি না। আমি আরও উল্লেখ করব যে আপনি যে কারণটি করছেন এটি কারণ আপনি যে সার্ভার-সাইড মডেলটি পাঠাচ্ছেন সেটি ক্লায়েন্টের পক্ষে ব্যবহার করা সুবিধাজনক নয় convenient এই জাতীয় পরিস্থিতি মোকাবেলার জন্য, আমার দল একটি ক্লায়েন্ট-সাইড মডেল তৈরি করে যা আমরা সার্ভার-সাইড মডেলটিকে ডিসিজারাইজেশন করার পরে অনুবাদ করি। আমরা ক্লায়েন্ট-সাইড এপিআইতে এই সমস্তগুলি আবৃত করি। আপনি যখন দেখিয়েছেন তার চেয়ে জটিলতর উদাহরণগুলি পাওয়া শুরু করলে, এই পদ্ধতিটি ক্লায়েন্টকে খারাপ সার্ভারের মডেল সিদ্ধান্তগুলি / এপিস থেকে রক্ষা করে কারণ এটি খুব সহায়ক।
spierce7

আপনি যেটিকে "সেরা পদ্ধতির" বলে দাবি করেছেন তাতে আমি একমত নই। আমি যে সমস্যাটি দেখছি তা হ'ল একটি ডেটা ক্লাসে একটি মান সেট করতে চাওয়া খুব সাধারণ and উদাহরণস্বরূপ, একটি স্ট্রিংকে একটি ইন্টারে পার্স করা। একটি ডেটা ক্লাসে কাস্টম গেটার / সেটারগুলি কেবল দরকারী নয়, প্রয়োজনীয়; অন্যথায়, আপনি জাভা বিন পজেওদের সাথে রয়ে গেছেন যা কিছুই করেন না এবং তাদের আচরণ + বৈধতা অন্য কোনও শ্রেণিতে অন্তর্ভুক্ত।
অভিজিৎ সরকার

আমি যা বলেছিলাম তা হ'ল "বেশিরভাগ ক্ষেত্রে এটি সম্ভবত সেরা পন্থা"। বেশিরভাগ ক্ষেত্রে, নির্দিষ্ট পরিস্থিতি তৈরি না হওয়া অবধি ডেভদের তাদের মডেল এবং অ্যালগরিদম / ব্যবসায়িক যুক্তির মধ্যে একটি স্পষ্ট বিভাজন থাকা উচিত, যেখানে তাদের অ্যালগোরিদম থেকে প্রাপ্ত ফলাফলটি সম্ভবত সম্ভাব্য ফলাফলের বিভিন্ন রাজ্যের প্রতিনিধিত্ব করে। কোটলিন সিলড ক্লাস এবং ডেটা ক্লাস সহ এটির জন্য দুর্দান্ত। আপনার উদাহরণের জন্য parsing a string into an int, আপনি আপনার মডেল শ্রেণিতে অ-
সংখ্যাযুক্ত

... মডেল এবং ব্যবসায়িক যুক্তিগুলির মধ্যে লাইনটি কাদামাটি করার অনুশীলন সর্বদা কম রক্ষণাবেক্ষণযোগ্য কোডের দিকে নিয়ে যায় এবং আমি যুক্তি দিই যে এটি একটি বিরোধী-নিদর্শন। সম্ভবত আমি তৈরি ডেটা ক্লাসগুলির 99% হ'ল অপরিবর্তনীয় / অভাবের সেটার। আমি মনে করি আপনি নিজের দলের মডেলগুলি অপরিবর্তনীয় রাখার সুবিধাগুলি সম্পর্কে পড়তে সত্যিই কিছুটা সময় উপভোগ করবেন। অপরিবর্তনীয় মডেলগুলির সাথে আমি গ্যারান্টি দিতে পারি যে আমার মডেলগুলি ঘটনাক্রমে কোডের অন্য কোনও এলোমেলো জায়গায় সংশোধন করা হচ্ছে না, যা পার্শ্ব-প্রতিক্রিয়া হ্রাস করে এবং আবারও রক্ষণাবেক্ষণযোগ্য কোডের দিকে নিয়ে যায়। অর্থাত্ কোটলিন পৃথক হয়নি Listএবং MutableListবিনা কারণে।
spierce7

31

আপনি এরকম কিছু চেষ্টা করতে পারেন:

data class Test(private val _value: Int) {
  val value = _value
    get(): Int {
      return if (field < 0) 0 else field
    }
}

assert(1 == Test(1).value)
assert(0 == Test(0).value)
assert(0 == Test(-1).value)

assert(1 == Test(1)._value) // Fail because _value is private
assert(0 == Test(0)._value) // Fail because _value is private
assert(0 == Test(-1)._value) // Fail because _value is private
  • একটি ডেটা ক্লাসে আপনাকে অবশ্যই প্রাথমিক কন্সট্রাক্টরের পরামিতিগুলি হয় valবা এর সাথে চিহ্নিত করতে হবে var

  • আমি এর মান বরাদ্দ করছি _valueকরার valueঅর্ডার সম্পত্তি জন্য পছন্দসই নাম ব্যবহার করতে হবে।

  • আপনার বর্ণিত যুক্তি দিয়ে সম্পত্তিটির জন্য আমি একটি কাস্টম অ্যাকসেসর সংজ্ঞায়িত করেছি।


4
আইডিইতে আমি একটি ত্রুটি পেয়েছি, এটি বলে যে "এই সম্পত্তির কোনও ব্যাকিং ফিল্ড নেই বলে এখানে প্রাথমিক সূচক অনুমোদিত নয়"
চেং

6

উত্তরটি নির্ভর করে আপনি প্রকৃতপক্ষে কী ক্ষমতা ব্যবহার করে তা নির্ভর dataকরে। @ পেড্রন একটি নিফটি ট্রিক (উন্নত সংস্করণ) উল্লেখ করেছেন:

data class Test(private val _value: Int) {
    val value: Int
        get() = if (_value < 0) 0 else _value
}

এটি প্রত্যাশার মতো কাজ করবে, ei এর একটি ক্ষেত্র রয়েছে, একটি গিটার আছে, ডান equals, hashcodeএবং component1। ধরাটি এটি toStringএবং copyঅদ্ভুত:

println(Test(1))          // prints: Test(_value=1)
Test(1).copy(_value = 5)  // <- weird naming

আপনার সমস্যাটি সমাধান করার জন্য toStringএটি নিজের হাতে নতুন করে সংজ্ঞায়িত করতে পারে। আমি প্যারামিটারের নাম ঠিক করার কোনও উপায় জানি না তবে একেবারেই ব্যবহার করব না data


2

আমি জানি এটি একটি পুরানো প্রশ্ন তবে এটি মনে হয় কেউ ব্যক্তিগত মূল্য এবং ব্যক্তিগত কাস্টম লেখককে এভাবে লেখার সম্ভাবনা উল্লেখ করেনি:

data class Test(private val value: Int) {
    fun getValue(): Int = if (value < 0) 0 else value
}

এটি পুরোপুরি বৈধ হওয়া উচিত কারণ কোটলিন ব্যক্তিগত ক্ষেত্রে ডিফল্ট গেটার তৈরি করতে পারে না।

তবে অন্যথায় আমি স্পিয়ার্স with এর সাথে অবশ্যই একমত যে ডেটা ক্লাসগুলি ডেটা ধরে রাখার জন্য এবং সেখানে "ব্যবসায়" যুক্তি আপনার হার্ডকোডিং এড়ানো উচিত।


আমি আপনার সমাধানের সাথে একমত আছি তবে কোডের চেয়ে আপনাকে এটিকে কল করতে হবে val value = test.getValue() এবং অন্যান্য গেটের মতো নয় val value = test.value
gori

হ্যাঁ. এটাই সঠিক. আপনি যদি সবসময় জাভা থেকে কল করেন তবে এটি কিছুটা আলাদা.getValue()
বায়ো 7007

1

আমি আপনার উত্তরটি দেখেছি, আমি সম্মত হয়েছি যে ডেটা ক্লাসগুলি কেবলমাত্র ডেটা রাখার জন্যই বোঝানো হয়, তবে কখনও কখনও আমাদের সেগুলি থেকে কিছুটা তৈরি করার প্রয়োজন হয়।

এখানে আমি আমার ডেটা ক্লাসটি দিয়ে যা করছি, আমি ভাল থেকে ভেরিতে কয়েকটি বৈশিষ্ট্য পরিবর্তন করেছি এবং সেগুলি কনস্ট্রাক্টরে ওভারিড করেছি।

তাই ভালো:

data class Recording(
    val id: Int = 0,
    val createdAt: Date = Date(),
    val path: String,
    val deleted: Boolean = false,
    var fileName: String = "",
    val duration: Int = 0,
    var format: String = " "
) {
    init {
        if (fileName.isEmpty())
            fileName = path.substring(path.lastIndexOf('\\'))

        if (format.isEmpty())
            format = path.substring(path.lastIndexOf('.'))

    }


    fun asEntity(): rc {
        return rc(id, createdAt, path, deleted, fileName, duration, format)
    }
}

ক্ষেত্রগুলিকে কেবল পরিবর্তনযোগ্য করে তোলা যাতে আপনি আরম্ভের সময় এগুলি সংশোধন করতে পারেন এটি একটি খারাপ অভ্যাস। কনস্ট্রাক্টরকে ব্যক্তিগত করা আরও ভাল হবে এবং তারপরে একটি ফাংশন তৈরি করুন যা নির্মাণকারীর (যেমন fun Recording(...): Recording { ... }) হিসাবে কাজ করে । এছাড়াও একটি ডেটা ক্লাস আপনি যা চান তা নয়, কারণ অ-ডেটা ক্লাসের সাহায্যে আপনি আপনার কনস্ট্রাক্টর প্যারামিটারগুলি থেকে আপনার সম্পত্তিগুলি আলাদা করতে পারেন। আপনার শ্রেণীর সংজ্ঞায় আপনার পরিবর্তনীয় উদ্দেশ্যগুলির সাথে স্পষ্ট হওয়া ভাল। যদি সেই ক্ষেত্রগুলিও যাইহোক পরিবর্তিত হতে পারে, তবে একটি ডেটা ক্লাস ভাল, তবে আমার প্রায় সমস্ত ডেটা ক্লাস অপরিবর্তনীয়।
spierce7

@ স্পিয়েরসি 7 একটি নিচে ভোট প্রাপ্য কি সত্যিই খারাপ? যাইহোক, এই সমাধানটি আমার পক্ষে ভাল মানায়, এটির পক্ষে এত বেশি কোডিং প্রয়োজন হয় না এবং এটি হ্যাশ রাখে এবং সমান।
সাইমু

0

এটি কোটলিনের এক (অন্যদের মধ্যে) বিরক্তিকর ত্রুটি বলে মনে হচ্ছে।

দেখে মনে হচ্ছে একমাত্র যুক্তিসঙ্গত সমাধান যা ক্লাসটির পশ্চাদপদ সামঞ্জস্যতা পুরোপুরি রাখে তা হ'ল এটিকে একটি নিয়মিত শ্রেণিতে রূপান্তর করা (কোনও "ডেটা" শ্রেণিতে নয়) এবং হাতে (আইডিইর সহায়তায়) পদ্ধতিগুলি প্রয়োগ করে: হ্যাশকোড ( ), সমান (), টু স্ট্রিং (), অনুলিপি () এবং উপাদান এন ()

class Data3(i: Int)
{
    var i: Int = i

    override fun equals(other: Any?): Boolean
    {
        if (this === other) return true
        if (other?.javaClass != javaClass) return false

        other as Data3

        if (i != other.i) return false

        return true
    }

    override fun hashCode(): Int
    {
        return i
    }

    override fun toString(): String
    {
        return "Data3(i=$i)"
    }

    fun component1():Int = i

    fun copy(i: Int = this.i): Data3
    {
        return Data3(i)
    }

}

4
নিশ্চিত নয় যে আমি এটিকে একটি অপূর্ণতা বলব। এটি কেবল ডেটা শ্রেণির বৈশিষ্ট্যের সীমাবদ্ধতা, যা জাভা অফার করে এমন কোনও বৈশিষ্ট্য নয়।
spierce7

0

আপনার যা প্রয়োজন তা ভঙ্গ না করে অর্জন করার জন্য আমি নিম্নলিখিতটি সর্বোত্তম পন্থা হিসাবে পেয়েছি equalsএবং hashCode:

data class TestData(private var _value: Int) {
    init {
        _value = if (_value < 0) 0 else _value
    }

    val value: Int
        get() = _value
}

// Test value
assert(1 == TestData(1).value)
assert(0 == TestData(-1).value)
assert(0 == TestData(0).value)

// Test copy()
assert(0 == TestData(-1).copy().value)
assert(0 == TestData(1).copy(-1).value)
assert(1 == TestData(-1).copy(1).value)

// Test toString()
assert("TestData(_value=1)" == TestData(1).toString())
assert("TestData(_value=0)" == TestData(-1).toString())
assert("TestData(_value=0)" == TestData(0).toString())
assert(TestData(0).toString() == TestData(-1).toString())

// Test equals
assert(TestData(0) == TestData(-1))
assert(TestData(0) == TestData(-1).copy())
assert(TestData(0) == TestData(1).copy(-1))
assert(TestData(1) == TestData(-1).copy(1))

// Test hashCode()
assert(TestData(0).hashCode() == TestData(-1).hashCode())
assert(TestData(1).hashCode() != TestData(-1).hashCode())

যাহোক,

প্রথমত, মনে রাখবেন যে _valueহয় varনা val, কিন্তু অন্য দিকে, যেহেতু এটি ব্যক্তিগত এবং, থেকে এটা মোটামুটি সহজ নিশ্চিত যে এটা বর্গ মধ্যে পরিবর্তন করা হয় না এর ডেটা শ্রেণীর উত্তরাধিকারসূত্রে করা যাবে না।

দ্বিতীয়ত, নামকরণ করা toString()হলে _valueতার থেকে কিছুটা আলাদা ফলাফল তৈরি করে valueতবে এটি ধারাবাহিক এবং TestData(0).toString() == TestData(-1).toString()


@ স্পিয়ার্স 7 N না, তাই না। _valueআর ডি ব্লকে পরিবর্তন করা হচ্ছে equalsএবং hashCode তা ভাঙ্গা হয়নি।
ছিটিয়ে দিন

আপনি এই সঙ্গে আউট আমাকে সাহায্য করতে পারেন stackoverflow.com/questions/64015108/...
কৃশ
আমাদের সাইট ব্যবহার করে, আপনি স্বীকার করেছেন যে আপনি আমাদের কুকি নীতি এবং গোপনীয়তা নীতিটি পড়েছেন এবং বুঝতে পেরেছেন ।
Licensed under cc by-sa 3.0 with attribution required.