এই উদাহরণটি কীভাবে মডেল করবেন
এটি কীভাবে পাঠকের মনডের সাথে মডেল করা যায়?
আমি নিশ্চিত নই যে এটি পাঠকের সাথে মডেল করা উচিত কিনা, তবুও এটি হতে পারে:
- ক্লাসগুলিকে ফাংশন হিসাবে এনকোডিং করা যা কোডটি প্লেয়ারের সাথে আরও সুন্দর করে তোলে
- পাঠকের সাথে বোঝার জন্য এবং এটি ব্যবহারের জন্য ফাংশনগুলি রচনা করা
শুরুর ঠিক ঠিক আগে আমাকে ছোট্ট নমুনা কোড সামঞ্জস্য সম্পর্কে আপনাকে জানাতে হবে যা আমি এই উত্তরের জন্য উপকারী বলে মনে করেছি। প্রথম পরিবর্তনটি FindUsers.inactive
পদ্ধতি সম্পর্কে । আমি এটিকে ফিরে আসতে List[String]
দিলাম যাতে ঠিকানাগুলির তালিকাটি UserReminder.emailInactive
পদ্ধতিতে ব্যবহার করা যায় । আমি পদ্ধতিগুলিতে সহজ বাস্তবায়নও যুক্ত করেছি। শেষ অবধি, নমুনাটি হ'ল পাঠক মোনাডের হ্যান্ড-রোলড সংস্করণটি ব্যবহার করবে:
case class Reader[Conf, T](read: Conf => T) { self =>
def map[U](convert: T => U): Reader[Conf, U] =
Reader(self.read andThen convert)
def flatMap[V](toReader: T => Reader[Conf, V]): Reader[Conf, V] =
Reader[Conf, V](conf => toReader(self.read(conf)).read(conf))
def local[BiggerConf](extractFrom: BiggerConf => Conf): Reader[BiggerConf, T] =
Reader[BiggerConf, T](extractFrom andThen self.read)
}
object Reader {
def pure[C, A](a: A): Reader[C, A] =
Reader(_ => a)
implicit def funToReader[Conf, A](read: Conf => A): Reader[Conf, A] =
Reader(read)
}
মডেলিং পদক্ষেপ 1. ফাংশন হিসাবে ক্লাস এনকোডিং
সম্ভবত এটি optionচ্ছিক, আমি নিশ্চিত নই, তবে পরে এটি বোঝার জন্য আরও ভাল দেখায়। দ্রষ্টব্য, যে ফলস্বরূপ ফাংশন তীক্ষ্ণ হয় এটি প্রাক্তন কনস্ট্রাক্টর আর্গুমেন্টকে তাদের প্রথম প্যারামিটার (পরামিতি তালিকা) হিসাবে গ্রহণ করে। ঐ দিকে
class Foo(dep: Dep) {
def bar(arg: Arg): Res = ???
}
হয়ে যায়
object Foo {
def bar: Dep => Arg => Res = ???
}
মনে রাখবেন যে প্রতিটি Dep
, Arg
, Res
ধরনের সম্পূর্ণরূপে অবাধ হতে পারে: একটি tuple, একটি ফাংশান বা একটা সহজ প্রকার।
প্রাথমিক সমন্বয়ের পরে নমুনা কোডটি এখানে ফাংশনে রূপান্তরিত হয়েছে:
trait Datastore { def runQuery(query: String): List[String] }
trait EmailServer { def sendEmail(to: String, content: String): Unit }
object FindUsers {
def inactive: Datastore => () => List[String] =
dataStore => () => dataStore.runQuery("select inactive")
}
object UserReminder {
def emailInactive(inactive: () => List[String]): EmailServer => () => Unit =
emailServer => () => inactive().foreach(emailServer.sendEmail(_, "We miss you"))
}
object CustomerRelations {
def retainUsers(emailInactive: () => Unit): () => Unit =
() => {
println("emailing inactive users")
emailInactive()
}
}
এখানে লক্ষ্য করার একটি বিষয় হ'ল নির্দিষ্ট ফাংশনগুলি পুরো অবজেক্টের উপর নির্ভর করে না, তবে কেবল প্রত্যক্ষভাবে ব্যবহৃত অংশগুলিতে। ওওপি সংস্করণে UserReminder.emailInactive()
উদাহরণটি userFinder.inactive()
এখানে কল করবে যেখানে এটি কেবল কল করে inactive()
- প্রথম প্যারামিটারে কোনও ফাংশন এটিতে চলে গেছে।
দয়া করে নোট করুন, কোডটি প্রশ্ন থেকে তিনটি পছন্দসই বৈশিষ্ট্য দেখায়:
- প্রতিটি কার্যকারিতা কী ধরণের নির্ভরশীলতার প্রয়োজন তা স্পষ্ট
- অন্যের থেকে একটি কার্যকারিতার নির্ভরতা আড়াল করে
retainUsers
পদ্ধতিটি ডেটাস্টোর নির্ভরতা সম্পর্কে জানতে হবে না
মডেলিং পদক্ষেপ 2 ফাংশন রচনা এবং এগুলি চালনার জন্য পাঠক ব্যবহার করে
পাঠক মনাদ আপনাকে কেবলমাত্র ফাংশন রচনা করতে দেয় যা সমস্ত একই ধরণের উপর নির্ভর করে। এটি প্রায়শই একটি মামলা হয় না। আমাদের উদাহরণ
FindUsers.inactive
উপর Datastore
এবং UserReminder.emailInactive
উপর নির্ভর করে EmailServer
। এই সমস্যাটি সমাধানের জন্য কেউ একটি নতুন প্রকার (প্রায়শই কনফিগার হিসাবে পরিচিত) প্রবর্তন করতে পারে যার মধ্যে সমস্ত নির্ভরতা থাকে তবে ফাংশনগুলি পরিবর্তন করুন যাতে তারা সকলেই এর উপর নির্ভর করে এবং কেবল এটি থেকে প্রাসঙ্গিক ডেটা গ্রহণ করে। এটি নির্ভরশীলতা পরিচালনার দৃষ্টিকোণ থেকে স্পষ্টতই ভুল কারণ আপনি এই ফাংশনগুলিকে এমন ধরণের উপরও নির্ভরশীল করেন যেগুলি সম্পর্কে তারা প্রথম স্থানেই জানেন না।
ভাগ্যক্রমে এটি সক্রিয়, এটি ফাংশনটি কাজ করার একটি উপায় রয়েছে Config
এমনকি যদি এটি প্যারামিটার হিসাবে কেবল এর কিছু অংশ গ্রহণ করে। এটি একটি পদ্ধতি বলা হয় local
, এটি পাঠকের সংজ্ঞায়িত। এটি থেকে প্রাসঙ্গিক অংশটি বের করার একটি উপায় সরবরাহ করা প্রয়োজন Config
।
এই জ্ঞানটি হাতের উদাহরণটিতে প্রয়োগ হয়েছে যা দেখতে এরকম হবে:
object Main extends App {
case class Config(dataStore: Datastore, emailServer: EmailServer)
val config = Config(
new Datastore { def runQuery(query: String) = List("john.doe@fizzbuzz.com") },
new EmailServer { def sendEmail(to: String, content: String) = println(s"sending [$content] to $to") }
)
import Reader._
val reader = for {
getAddresses <- FindUsers.inactive.local[Config](_.dataStore)
emailInactive <- UserReminder.emailInactive(getAddresses).local[Config](_.emailServer)
retainUsers <- pure(CustomerRelations.retainUsers(emailInactive))
} yield retainUsers
reader.read(config)()
}
কনস্ট্রাক্টর প্যারামিটার ব্যবহারের ক্ষেত্রে সুবিধা
এই জাতীয় "ব্যবসায়িক অ্যাপ্লিকেশন" এর জন্য পাঠক মোনাডকে কোন দিক দিয়ে কেবল কনস্ট্রাক্টর প্যারামিটার ব্যবহার না করা ভাল?
আমি আশা করি যে এই উত্তরটি প্রস্তুত করে আমি নিজের পক্ষে বিচার করা আরও সহজ করেছি যে এটি কোন দিক থেকে সরল নির্মাতাদের মারবে। তবুও যদি আমি এগুলি গণনা করি তবে এখানে আমার তালিকা। দাবি অস্বীকার: আমার ওওপি ব্যাকগ্রাউন্ড রয়েছে এবং আমি এগুলি ব্যবহার না করায় পঠক এবং ক্লেসলির সম্পূর্ণ প্রশংসা করতে পারি না।
- ইউনিফর্মিটি - বোঝার জন্য কতটা সংক্ষিপ্ত / দীর্ঘ তা কোন ম্যাটর নয়, এটি কেবল একটি পাঠক এবং আপনি এটি সহজেই অন্য একটি উদাহরণ দিয়ে রচনা করতে পারেন, সম্ভবত কেবল আরও একটি কনফিগার ধরণের পরিচয় করিয়ে দেওয়া হয়েছে এবং এর
local
উপরে কয়েকটি কল ছিটানো রয়েছে । এই বিন্দুটি আইএমও বরং স্বাদের বিষয়, কারণ আপনি যখন কনস্ট্রাক্টর ব্যবহার করেন তখন কেউ আপনার পছন্দ মতো জিনিসগুলি রচনা করতে বাধা দেয় না, যদি না কেউ নির্বোধের মতো কাজ করেন, যেমন ওওপিতে খারাপ অভ্যাস হিসাবে বিবেচিত হয়।
- পাঠক, একটি একসংখ্যা তাই এটা যে এর সাথে সম্পর্কিত সকল সুবিধা পায় -
sequence
, traverse
পদ্ধতি বিনামূল্যে জন্য প্রয়োগ।
- কিছু ক্ষেত্রে আপনি কেবল একবারে পাঠক তৈরি করা ভাল এবং বিস্তৃত কনফিগের জন্য এটি ব্যবহার করতে পারেন। কনস্ট্রাক্টরগুলির সাথে কেউ আপনাকে এটি করতে বাধা দেয় না, আপনাকে প্রতিটি কনফিগার ইনকামিংয়ের জন্য নতুন অবজেক্ট গ্রাফটি নতুন করে তৈরি করতে হবে। যদিও এতে আমার কোনও সমস্যা নেই (আমি আবেদন করার প্রতিটি অনুরোধের ভিত্তিতে এটি করাও পছন্দ করি), কারণ যে কারণে আমি কেবলমাত্র অনুমান করতে পারি তা অনেক লোকের কাছে এটি সুস্পষ্ট ধারণা নয়।
- পাঠক আপনাকে আরও বেশি কার্যকারিতা ব্যবহারের দিকে ঠেলে দেয় যা মূলত এফপি স্টাইলে লিখিত প্রয়োগের সাথে আরও ভাল খেলবে।
- পাঠক উদ্বেগ আলাদা করে; আপনি নির্ভরতা সরবরাহ না করে লজিক সংজ্ঞায়িত করতে পারবেন, সবকিছু দিয়ে ইন্টারঅ্যাক্ট করতে পারবেন। আসলে সরবরাহ পরে, পৃথকভাবে। (এই বিষয়টির জন্য ধন্যবাদ কেন স্ক্র্যামব্লার) Thanks এটি প্রায়শই পাঠকের সুবিধার জন্য শোনা যায়, তবু এটি সরল নির্মাতাদের দ্বারাও সম্ভব with
আমি রিডারে যা পছন্দ করি না তাও বলতে চাই।
- বিপণন। কখনও কখনও আমি অনুভব করি যে রিডারটি সমস্ত ধরণের নির্ভরতার জন্য বাজারজাত করা হয়, পার্থক্য ছাড়াই যদি সে সেশন কুকি বা ডেটাবেস থাকে। আমার কাছে এই উদাহরণ থেকে ইমেল সার্ভার বা সংগ্রহস্থলের মতো ব্যবহারিকভাবে ধ্রুবক অবজেক্টের জন্য রিডার ব্যবহার করার সামান্য জ্ঞান নেই। এই ধরনের নির্ভরতার জন্য আমি প্লেইন কনস্ট্রাক্টর এবং / অথবা আংশিকভাবে প্রয়োগ ফাংশনগুলি আরও ভালভাবে পাই। মূলত রিডার আপনাকে নমনীয়তা দেয় যাতে আপনি প্রতিটি কলে আপনার নির্ভরতা নির্দিষ্ট করতে পারেন, তবে আপনার যদি সত্যিই এটির প্রয়োজন না হয় তবে আপনি কেবল তার করটি প্রদান করেন।
- অন্তর্নিহিত ভারাক্রান্ততা - জড়িত ছাড়াই রিডার ব্যবহার করা উদাহরণটিকে পড়া শক্ত করে তোলে। অন্যদিকে, আপনি যখন ছদ্মবেশ ব্যবহার করে কোলাহলপূর্ণ অংশগুলি আড়াল করেন এবং কিছু ত্রুটি করেন, সংকলক মাঝে মাঝে আপনাকে ডেসিফার বার্তাগুলি বোঝা শক্ত করে দেয়।
- এর সাথে অনুষ্ঠান করা
pure
, local
এবং নিজস্ব কনফিগার ক্লাস তৈরি করা / এর জন্য টিপলস ব্যবহার করে। পাঠক আপনাকে এমন কিছু কোড যুক্ত করতে বাধ্য করেন যা সমস্যা ডোমেন সম্পর্কিত নয়, সুতরাং কোডটিতে কিছু শব্দ শোনার জন্য। অন্যদিকে, একটি অ্যাপ্লিকেশন যা কনস্ট্রাক্টর ব্যবহার করে প্রায়শই কারখানার প্যাটার্ন ব্যবহার করে, যা সমস্যা ডোমেনের বাইরের থেকেও তাই, এই দুর্বলতা এতটা গুরুতর নয়।
যদি আমি আমার ক্লাসগুলি ফাংশন সহ বস্তুতে রূপান্তর করতে না চাই তবে কী হবে?
তুমি চাও. আপনি প্রযুক্তিগতভাবে এড়াতে পারেন , তবে দেখুন যদি আমি FindUsers
ক্লাসকে অবজেক্টে রূপান্তর না করি তবে কী হবে look বোঝার জন্য সম্পর্কিত লাইনটি দেখতে পাবেন:
getAddresses <- ((ds: Datastore) => new FindUsers(ds).inactive _).local[Config](_.dataStore)
যা পাঠযোগ্য নয়, তাই না? মুল বক্তব্যটি হ'ল রিডার ফাংশনগুলিতে পরিচালনা করে, সুতরাং আপনার যদি ইতিমধ্যে তা না থাকে তবে আপনার সেগুলি ইনলাইন তৈরি করা দরকার যা প্রায়শই এত সুন্দর নয়।