ট্র্যাক
/
Swift
Swift
/
অনুশীলনী
/
উইন্ডোইং সিস্টেম
উইন্ডোইং সিস্টেম

উইন্ডোইং সিস্টেম

লার্নিং অনুশীলনী

ভূমিকা

স্ট্রাক্ট ও ক্লাস

স্ট্রাক্ট ও ক্লাস হলো Swift প্রোগ্রামিংয়ের দুটি প্রধান বিল্ডিং ব্লক। দুটোই সম্পর্কিত ডেটা ও ফাংশনকে স্বয়ংসম্পূর্ণ কার্যকারিতার এককে গুছিয়ে রাখার উপায়। আর আপনি যখন কোনো স্ট্রাক্ট বা ক্লাস ডিফাইন করেন, তখন Swift-এর ভেতরে ব্যবহারের জন্য একটি নতুন টাইপ ডিফাইন করছেন, ঠিক যেমন Int আর String-এর মতো টাইপ আপনি আগেই ব্যবহার করেছেন।

Swift-এ স্ট্রাক্ট আর ক্লাসের মধ্যে অনেক মিল আছে। আরও অনেক মিলের মধ্যে, দুটোই প্রপার্টিতে মান সংরক্ষণ করতে পারে এবং মেথড ব্যবহারের মাধ্যমে কার্যকারিতা প্রদান করে।

দুটোর প্রত্যেকটিই কিছু অতিরিক্ত কার্যকারিতা দেয়, যা এই অনুশীলনীর আওতার বাইরে।

স্ট্রাক্ট ও ক্লাস ডিফাইন করা

স্ট্রাক্ট আর ক্লাস দুটোই প্রায় একইভাবে ডিফাইন করা হয়। এরা শুরু হয় উপযুক্ত কিওয়ার্ড দিয়ে, তারপর আসে যে টাইপটি ডিফাইন করা হচ্ছে তার নাম, তারপর দ্বিতীয় বন্ধনী {}-এর মধ্যে বসে স্ট্রাক্ট/ক্লাসের বডি।

বডিতে থাকতে পারে স্টোরড প্রপার্টি, যেগুলো সাধারণ কনস্ট্যান্ট বা ভ্যারিয়েবলের মতোই ডিফাইন করা হয় এবং আচরণ করে।

struct CharacterStats {
  var health = 0.0
  var speed = 0
  var strength = 0
}

class GameCharacter {
  var stats = CharacterStats()
  var characterClass: String?
  var name: String?
  var active = false
  let id = makeRandomID()
}

ইনস্ট্যান্স

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

ওই টাইপের একটি ইনস্ট্যান্স তৈরি করতে আপনাকে টাইপের নাম লিখতে হবে, তারপর একটি জোড়া প্রথম বন্ধনী।

let someStats = CharacterStats()
let someCharacter = GameCharacter()

এতে এই টাইপের মান তৈরি হবে, যেখানে প্রপার্টিগুলো ডেফিনিশনে দেওয়া ডিফল্ট মান দিয়ে পূরণ হয়। খেয়াল রাখবেন, GameCharacter-এর name প্রপার্টির মতো অপশনাল ক্ষেত্রে, কোনো মান না দিলে প্রপার্টিটি ডিফল্টভাবে nil হবে, ঠিক যেমন সাধারণ অপশনাল টাইপ ডিফাইন করার সময় কোনো মান সাথে সাথে দেওয়া না হলে হয়।

স্ট্রাক্টের ক্ষেত্রে, Swift স্বয়ংক্রিয়ভাবে মেম্বারওয়াইজ ইনিশিয়ালাইজার নামের একটি জিনিস দেয়, যেখানে প্রথম বন্ধনীর ভেতরে স্ট্রাক্টের প্রপার্টিগুলোর মান দেওয়া যায়, যা ডেফিনিশনের ডিফল্ট মানগুলোকে ওভাররাইড করবে।

let differentStats = CharacterStats(health: 100.0, speed: 6, strength: 18)

প্রপার্টি অ্যাক্সেস করা

স্ট্রাক্ট আর ক্লাসের প্রপার্টি অ্যাক্সেস করা যায় ডট নোটেশন দিয়ে, যেখানে মানের নামের পরে একটি ডট (.) আর তারপর প্রপার্টির নাম থাকে। যদি কোনো স্ট্রাক্ট বা ক্লাসের প্রপার্টির নিজেরও প্রপার্টি থাকে, তাহলে এই ডট নোটেশন দিয়ে ওই নেস্টেড প্রপার্টিগুলোও অ্যাক্সেস করা যায়।

এই নোটেশন দিয়ে প্রপার্টির মান পড়া যায়, আর যেখানে অনুমতি আছে সেখানে তা বদলানোও যায়।

someStats.health
// => 0
someCharacter.name
// => nil

someStats.health = 87.3
someStats.health
// => 87.3
someCharacter.name = "Luther"
someCharacter.name
// => "Luther"

someCharacter.id  = "new id"
// Error: Cannot assign to property: 'id' is a 'let' constant

মেথড

প্রপার্টির মতো, যা আপনার স্ট্রাক্ট আর ক্লাসে ডেটা সংরক্ষণ করে, আপনি মেথডও ডিফাইন করতে পারেন, যা আপনার স্ট্রাক্ট বা ক্লাসে ফাংশন সংরক্ষণ করে।

মেথড সাধারণ ফাংশনের মতোই ডিফাইন করা হয়, শুধু স্ট্রাক্ট বা ক্লাসের বডির ভেতরে। খেয়াল রাখবেন, কোনো ফাংশন যদি স্ট্রাক্টের কোনো প্রপার্টির মান বদলায়, তাহলে তার আগে অবশ্যই mutating কিওয়ার্ড থাকতে হবে। এছাড়া, কোনো মেথড যদি কোনো প্রপার্টি বদলাতে পারে, তাহলে সেই প্রপার্টি let-এর বদলে var দিয়ে ডিফাইন করতে হবে, ঠিক সাধারণ ভ্যারিয়েবলের মতো।

struct CharacterStats {
  var health = 0.0
  var speed = 0
  var strength = 0

  mutating func takeHit(_ damage: Double) {
    health = max(0.0, health - damage)
  }
  func canLift(_ weight: Int) -> Bool {
    weight < strength * 100
  }
}

class GameCharacter {
  var stats = CharacterStats()
  var characterClass: String?
  var name: String?
  var active = false
  let id: String = makeRandomID()

  func takesDamage(_ damage: Double) {
    stats.takeHit(damage)
    if stats.health <= 0 {
      active = false
    }
  }
  func sayName() -> String {
    return "My name is \(name ?? "no one"), my class is \(characterClass ?? "undetermined")"
  }
  func lift(_ weight: Int) -> String {
    if stats.canLift(weight) {
      return "No problem!"
    } else {
      return "Ooof! No way."
    }
  }
}

এই মেথডগুলো প্রপার্টির মতোই ডট নোটেশন দিয়ে কল করা যায়।

var myChar = GameCharacter()
myChar.stats = CharacterStats(health: 72.8, speed: 19, strength: 6)
myChar.active = true

myChar.lift(750)
// => "Ooof! No way."
myChar.takesDamage(80)
myChar.active
// => false

Self

স্ট্রাক্ট বা ক্লাসের প্রতিটি ইনস্ট্যান্সের self নামে একটি অন্তর্নিহিত প্রপার্টি থাকে, যা ইনস্ট্যান্সটিকেই বোঝায়। self-এর সবচেয়ে কমন ব্যবহার হলো একই নামের একটি ইনস্ট্যান্স প্রপার্টি আর একটি প্যারামিটারের মধ্যে পার্থক্য স্পষ্ট করা:

struct Position {
    var x = 0

    mutating func move(x: Int) {
        // Here, 'self.x' refers to the struct's property,
        // while 'x' refers to the parameter passed to the method.
        self.x = x
    }
}

Swift টাইপ আবার একটি বড় হাতের অক্ষরে লেখা Self কিওয়ার্ডও দেয়, যা ইনস্ট্যান্সের বদলে ইনস্ট্যান্সের টাইপকে বোঝায়।

নির্দেশনা

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

                  <--------------------- screenSize.width --------------------->

       ^          ╔════════════════════════════════════════════════════════════╗
       |          ║                                                            ║
       |          ║          position.x,_                                      ║
       |          ║          position.y  \                                     ║
       |          ║                       \<----- size.width ----->            ║
       |          ║                 ^      *──────────────────────┐            ║
       |          ║                 |      │        title         │            ║
       |          ║                 |      ├──────────────────────┤            ║
screenSize.height ║                 |      │                      │            ║
       |          ║            size.height │                      │            ║
       |          ║                 |      │       contents       │            ║
       |          ║                 |      │                      │            ║
       |          ║                 |      │                      │            ║
       |          ║                 v      └──────────────────────┘            ║
       |          ║                                                            ║
       |          ║                                                            ║
       v          ╚════════════════════════════════════════════════════════════╝

1. একটি Size স্ট্রাক্ট ডিফাইন করুন

width ও height নামের দুটি Int প্রোপার্টিসহ Size নামে একটি স্ট্রাক্ট ডিফাইন করুন, যেগুলো যথাক্রমে উইন্ডোর বর্তমান প্রস্থ ও উচ্চতা সংরক্ষণ করে। প্রাথমিক প্রস্থ ও উচ্চতা যথাক্রমে 80 ও 60 হওয়া উচিত। এমন একটি মেথড resize(newWidth:newHeight:) যোগ করুন যা নতুন প্রস্থ ও উচ্চতার প্যারামিটার নেয় এবং নতুন আকার প্রতিফলিত করতে প্রোপার্টিগুলো পরিবর্তন করে।

let size1080x764 = Size(width: 1080, height: 764)
// Returns Size
var size1200x800 = size1080x764
// Returns Size
size1200x800.resize(newWidth: 1200, newHeight: 800)
size1200x800.height
// Returns 800

2. একটি Position স্ট্রাক্ট ডিফাইন করুন

x ও y নামের দুটি Int প্রোপার্টিসহ Position নামে একটি স্ট্রাক্ট ডিফাইন করুন, যেগুলো যথাক্রমে উইন্ডোর উপরের বাম কোণের বর্তমান অনুভূমিক ও উল্লম্ব অবস্থান সংরক্ষণ করে। x ও y-এর প্রাথমিক মান প্রত্যেকটি 0 হওয়া উচিত। (0, 0) অবস্থানটি স্ক্রিনের উপরের বাম কোণ, যেখানে ডানে সরলে x-এর মান বাড়ে এবং নিচে সরলে y-এর মান বাড়ে।

এমন একটি মেথড moveTo(newX:newY:) যোগ করুন যা নতুন x ও y প্যারামিটার নেয় এবং নতুন অবস্থান প্রতিফলিত করতে প্রোপার্টিগুলো পরিবর্তন করে।

var point = Position(x: 10, y: 20)
// Returns Position
point.moveTo(newX: 100, newY: -100)
point.y
// Returns -100

3. একটি Window ক্লাস ডিফাইন করুন

নিচের প্রোপার্টিগুলোসহ একটি উইন্ডো ক্লাস ডিফাইন করুন:

  • title : String, প্রাথমিক মান "New Window"
  • screenSize : Size, width = 800 ও height = 600 সহ ধ্রুবক মান
  • size : Size, প্রাথমিক মান Size স্ট্রাক্টের ডিফল্ট মান
  • position : Position, প্রাথমিক মান Position স্ট্রাক্টের ডিফল্ট মান
  • contents : String?, প্রাথমিক মান nil

ক্লাসটির জন্য একটি খালি ইনিশিয়ালাইজারও ডিফাইন করা উচিত।

let window = Window()
window.title
// Returns "New Window"

4. উইন্ডো রিসাইজ করার একটি মেথড যোগ করুন

  • resize(to:) : (Size) -> () - এই মেথডটি ইনপুট হিসেবে একটি Size স্ট্রাক্ট নেয় এবং উইন্ডোটিকে নির্দিষ্ট আকারে রিসাইজ করার চেষ্টা করে। তবে নতুন আকার নির্দিষ্ট সীমা অতিক্রম করতে পারে না।
  • অনুমোদিত সর্বনিম্ন উচ্চতা বা প্রস্থ হলো 1। 1-এর কম চাওয়া উচ্চতা বা প্রস্থ 1-এ ক্লিপ করা হবে।
  • সর্বোচ্চ উচ্চতা ও প্রস্থ উইন্ডোর বর্তমান অবস্থানের উপর নির্ভর করে, উইন্ডোর প্রান্ত স্ক্রিনের প্রান্ত অতিক্রম করে যেতে পারে না। এই সীমার চেয়ে বড় মান যতটা বড় আকার নেওয়া সম্ভব তাতে ক্লিপ করা হবে। যেমন, উইন্ডোর অবস্থান x = 400, y = 300 এবং height = 400, width = 300-এ রিসাইজ চাওয়া হলে উইন্ডোটি height = 300, width = 300-এ রিসাইজ করা হবে, কারণ অনুরোধটি সম্পূর্ণভাবে মিটানোর জন্য y দিকে স্ক্রিনটি যথেষ্ট বড় নয়।

5. উইন্ডো সরানোর একটি মেথড যোগ করুন

  • move(to:) : (Position) -> () - এটি resize(to:)-এর মতোই, তবে এই মেথডটি আকারের বদলে উইন্ডোর অবস্থান অনুরোধকৃত মানে সমন্বয় করে। resize-এর মতোই নতুন অবস্থানও নির্দিষ্ট সীমা অতিক্রম করতে পারে না।
  • x ও y উভয়ের জন্য সর্বনিম্ন অবস্থান 0।
  • যেকোনো দিকে সর্বোচ্চ অবস্থান উইন্ডোর বর্তমান আকারের উপর নির্ভর করে; প্রান্তগুলো স্ক্রিনের প্রান্ত অতিক্রম করে যেতে পারে না। এই সীমার চেয়ে বড় মান যতটা বড় আকার নেওয়া সম্ভব তাতে ক্লিপ করা হবে। যেমন, উইন্ডোর আকার x = 250, y = 100 এবং x = 600, y = 200-এ সরানো চাওয়া হলে উইন্ডোটি x = 550, y = 200-এ সরানো হবে, কারণ অনুরোধটি সম্পূর্ণভাবে মিটানোর জন্য x দিকে স্ক্রিনটি যথেষ্ট বড় নয়।

6. উইন্ডোর টেক্সট হালনাগাদ ও উইন্ডোর তথ্য প্রদর্শনের মেথড যোগ করুন

  • update(title:) : (String) -> () - এই মেথডটি পাস করা স্ট্রিংয়ের মানে title প্রোপার্টি সেট করে।
  • update(text:) : (String?) -> () - এই মেথডটি পাস করা অপশনাল স্ট্রিংয়ের মানে contents প্রোপার্টি সেট করে।
  • display() : () -> String - এই মেথডটি উইন্ডোর বর্তমান অবস্থা বর্ণনা করে একটি স্ট্রিং রিটার্ন করে। উদাহরণস্বরূপ, যদি উইন্ডোর title হয় "My First Window", অবস্থান: x = 10, y = 100; আকার: width = 200, height = 150; এবং কনটেন্ট: "I 😍 my window", তাহলে এটি স্ট্রিংটি রিটার্ন করবে: "My First Window\nPosition: (10, 100), Size: (200 x 150)\nI 😍 my window\n" - যদি contents nil হয়, শেষ লাইনটি হবে "[This window intentionally left blank]"

7. Window ক্লাসের জন্য একটি ইনিশিয়ালাইজার তৈরি করুন

উইন্ডো সিস্টেমে একটি ইনিশিয়ালাইজার থাকা উচিত যাতে কাস্টম ইনপুট দিয়ে উইন্ডো তৈরি করা যায়। Window ক্লাসের জন্য আরেকটি ইনিশিয়ালাইজার তৈরি করুন। ইনিশিয়ালাইজারটি নিচের প্যারামিটারগুলো নেবে: title, contents, এবং দুটি অপশনাল প্যারামিটার size ও position। size ও position প্যারামিটারের ডিফল্ট মান হওয়া উচিত Size ও Position স্ট্রাক্টের ডিফল্ট মান।

let window = Window(title: "My First Window", contents: "I 😍 my window")
window.display()
// Returns "My First Window\nPosition: (0, 0), Size: (80 x 60)\nI 😍 my window\n"

let window2 = Window(title: "My Second Window", contents: "I 😍 my window", size: Size(width: 200, height: 150), position: Position(x: 10, y: 100))
window2.display()
// Returns "My Second Window\nPosition: (10, 100), Size: (200 x 150)\nI 😍 my window\n"
GitHub-এর মাধ্যমে সম্পাদনা করুন লিংকটি একটি নতুন উইন্ডো বা ট্যাবে খোলে
Swift Exercism

উইন্ডোইং সিস্টেম শুরু করতে প্রস্তুত?

Exercism-এ সাইন আপ করুন, Swift ট্র্যাকের 35টি কনসেপ্ট116টি অনুশীলনী আর সত্যিকারের মানুষের মেন্টরিং দিয়ে শিখুন ও দক্ষ হয়ে উঠুন, সম্পূর্ণ বিনামূল্যে।