مسیرها
/
Swift
Swift
/
تمرین‌ها
/
سیستم پنجره‌بندی
سیستم پنجره‌بندی

سیستم پنجره‌بندی

تمرین یادگیری

مقدمه

structها و classها

structها و classها دو مورد از بلوک‌های اصلی برنامه‌نویسی Swift هستند. هر دوی آن‌ها روشی برای گرد هم آوردن داده‌ها و توابع مرتبط در واحدهای خودبسنده‌ای از کارکرد هستند. وقتی یک struct یا class تعریف می‌کنید، در واقع یک نوع جدید تعریف می‌کنید که در Swift به کار می‌آید، دقیقاً همان‌طور که از انواعی که تا حالا با آن‌ها کار کرده‌اید استفاده می‌کنید، مانند Int و String.

structها و classها در Swift شباهت‌های زیادی به هم دارند. در میان شباهت‌های دیگر، هر دو می‌توانند مقادیر را در «ویژگی»ها نگه دارند و کارکرد خود را از طریق «متد»ها فراهم کنند.

هر کدام قابلیت‌های اضافی دیگری هم دارند که خارج از محدوده‌ی این تمرین است.

تعریف struct و class

هر دو تقریباً به یک شکل تعریف می‌شوند. با کلیدواژه‌ی مناسب شروع می‌شوند و پس از آن اسم نوعی می‌آید که تعریف می‌شود، سپس بدنه‌ی struct یا class در ادامه و بین «آکولاد»ها قرار می‌گیرد.

بدنه می‌تواند از «ویژگی»های ذخیره‌ای تشکیل شده باشد که درست مانند ثابت‌ها یا متغیرهای معمولی تعریف می‌شوند و رفتار می‌کنند.

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()
}

نمونه‌ها

همان‌طور که اشاره شد، تعریف یک struct یا class فقط تعریف یک «نوع» جدید است. این کار فقط نقشه‌ای است برای اینکه مقادیر آن نوع چه شکلی خواهند داشت، اما در واقع هیچ مقداری از آن نوع نمی‌سازد که شما با آن کار کنید.

برای ساختن یک «نمونه» از آن نوع، باید اسم نوع را بنویسید و پس از آن یک جفت پرانتز بگذارید.

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

این کار مقادیری از این نوع‌ها می‌سازد که ویژگی‌هایشان با مقدارهای پیش‌فرضِ داده‌شده در تعریف پر می‌شوند. توجه کنید که در حالت‌های اختیاری مانند ویژگی name در GameCharacter، اگر مقداری داده نشود، آن ویژگی به‌طور پیش‌فرض nil می‌شود، درست مانند تعریف انواع اختیاری معمولی که در آن‌ها مقدار بلافاصله داده نمی‌شود.

در مورد structها، Swift به‌طور خودکار چیزی به نام «مقدارساز عضو به عضو» فراهم می‌کند که در آن می‌توان مقادیر ویژگی‌های struct را داخل پرانتزها داد؛ این مقادیر جای مقدارهای پیش‌فرض تعریف را می‌گیرند.

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

دسترسی به ویژگی‌ها

می‌توان با «نشانه‌گذاری نقطه‌ای» به ویژگی‌های struct و class دسترسی پیدا کرد؛ در این روش، پس از اسم مقدار یک نقطه (.) و بعد اسم ویژگی می‌آید. اگر یک ویژگی struct یا class خودش ویژگی‌هایی داشته باشد، با همین نشانه‌گذاری نقطه‌ای می‌توان به این ویژگی‌های تودرتو هم دسترسی پیدا کرد.

این نشانه‌گذاری هم برای خواندن مقدار ویژگی به کار می‌رود و هم، در جایی که مجاز باشد، برای تغییر آن.

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

متدها

ویژگی‌ها داده را در structها و classهای شما نگه می‌دارند؛ در کنار آن‌ها می‌توانید «متد»هایی هم تعریف کنید که توابع را در struct یا class شما ذخیره می‌کنند.

متدها درست مانند یک تابع معمولی تعریف می‌شوند، فقط داخل بدنه‌ی struct یا class. توجه کنید که اگر تابعی مقدار یک ویژگی را در یک struct تغییر دهد، باید پیش از آن کلیدواژه‌ی mutating بیاید. به‌علاوه، اگر متدی بتواند یک ویژگی را تغییر دهد، آن ویژگی باید با var تعریف شود، نه let، درست مانند متغیرهای معمولی.

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

هر نمونه‌ی یک struct یا class یک ویژگی ضمنی به اسم 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

ساختاری به اسم Size تعریف کنید که دو ویژگی Int دارد، width و height، که به ترتیب عرض و ارتفاع فعلی پنجره را نگه می‌دارند. عرض و ارتفاع اولیه باید به ترتیب 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

ساختاری به اسم Position تعریف کنید که دو ویژگی Int دارد، x و y، که به ترتیب موقعیت افقی و عمودی فعلی گوشه‌ی بالا-چپ پنجره را نگه می‌دارند. مقدارهای اولیه‌ی 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" باشد و position آن x = 10، y = 100؛ size آن width = 200، height = 150؛ و contents آن "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 تمرین و مربی‌گری انسانی واقعی یاد بگیرید و در آن استاد شوید، همه‌ی این‌ها رایگان.