Classes and Objects¶
Language: Español | English
Crespi supports object-oriented programming with classes, inheritance, and methods.
Defining a Class¶
Use class to define a class:
class Person(var name: String, var age: Int) {
fn introduce() {
print("I am " + this.name + ", " + str(this.age) + " years old")
}
}
Creating Instances¶
Create objects by calling the class directly:
var ana = Person("Ana", 25)
var luis = Person("Luis", 30)
ana.introduce() // I am Ana, 25 years old
luis.introduce() // I am Luis, 30 years old
The Constructor: constructor¶
The constructor method is called automatically when creating an instance:
class Rectangle {
constructor(width: Int, height: Int) {
this.width = width
this.height = height
print("Rectangle created: " + str(width) + "x" + str(height))
}
}
var rect = Rectangle(10, 5)
// Output: Rectangle created: 10x5
Primary constructors can be declared in the class header (Kotlin-style). Use var or let to make
parameters into instance properties:
var: Creates a mutable property that can be modified after constructionlet: Creates an immutable property that cannot be changed after construction- No modifier: The parameter is only used for construction, not stored as a property
class Point(let x: Int, let y: Int) {
}
var p = Point(3, 4)
print(p.x) // 3
print(p.y) // 4
// p.x = 10 // Error: cannot modify immutable property
Use var when you need mutable properties:
class Counter(var value: Int) {
}
var c = Counter(0)
c.value = 10 // OK: var properties are mutable
Additional constructors can delegate to the primary one:
class Point(let x: Int, let y: Int) {
constructor(tuple: (Int, Int)) : this(tuple[0], tuple[1])
}
Constructor Without Parameters¶
class Counter(var value: Int = 0) {
fn increment() {
this.value += 1
}
fn get() -> Int {
return this.value
}
}
var c = Counter()
c.increment()
c.increment()
print(c.get()) // 2
The this Reference¶
this references the current instance:
class Circle(var radius: Float) {
fn area() -> Float {
return 3.14159 * this.radius * this.radius
}
fn perimeter() -> Float {
return 2 * 3.14159 * this.radius
}
fn scale(factor: Float) {
this.radius = this.radius * factor
}
}
var circle = Circle(5.0)
print(circle.area()) // 78.53975
print(circle.perimeter()) // 31.4159
circle.scale(2.0)
print(circle.radius) // 10
Static Members and Blocks¶
Use static to define class-level fields, methods, and initialization blocks. Static members are
accessed on the class itself, not on instances.
class Config {
static let version = "1.0"
static var counter = 0
static fn bump() {
Config.counter = Config.counter + 1
}
static {
Config.bump()
}
}
print(Config.version) // "1.0"
print(Config.counter) // 1
Nested and Inner Classes¶
Use nested class for static nested types and inner class when you need access to the outer
instance. Inner class instances store the outer instance in this.__outer.
class Outer(var value) {
nested class StaticLabel {
fn label() {
return "static"
}
}
inner class InnerValue {
fn outerValue() {
return this.__outer.value
}
}
fn makeInner() {
return this.InnerValue()
}
}
var s = Outer.StaticLabel()
var o = Outer(10)
var i = o.makeInner()
print(i.outerValue()) // 10
You can also construct an inner class explicitly by passing the outer instance first:
var o = Outer(10)
var i = Outer.InnerValue(o)
Outside the class, use the explicit outer-argument form so the compiler can resolve the class unambiguously.
When you already have an instance, you can also call o.InnerValue() (inner classes only).
Instantiating Inner Classes from Outer Instances¶
When you call o.InnerValue() on an existing outer instance, the compiler uses the same runtime helper
as the interpreter to pass o along as the hidden this.__outer reference. This keeps
o.InnerValue() behavior consistent between interpreted and compiled code, so the outer
instance is always captured automatically.
Properties¶
Accessing Properties¶
class Point(let x: Int, let y: Int) {
}
var p = Point(3, 4)
print(p.x) // 3
print(p.y) // 4
Modifying Properties¶
Use var for mutable properties:
class Point(var x: Int, var y: Int) {
}
var p = Point(0, 0)
p.x = 10
p.y = 20
print(p.x) // 10
print(p.y) // 20
Dynamic Properties¶
You can add properties after creation:
class Object(var name: String = "object") {
}
var obj = Object()
obj.color = "red" // New property
obj.size = "large" // Another property
print(obj.color) // red
print(obj.size) // large
Methods¶
Methods with Return¶
class Calculator(var memory: Int = 0) {
fn add(a: Int, b: Int) -> Int {
return a + b
}
fn store(value: Int) {
this.memory = value
}
fn recall() -> Int {
return this.memory
}
}
var calc = Calculator()
print(calc.add(5, 3)) // 8
calc.store(100)
print(calc.recall()) // 100
Methods that Modify State¶
class BankAccount(var balance: Float) {
fn deposit(amount: Float) {
this.balance += amount
}
fn withdraw(amount: Float) -> Bool {
if amount > this.balance {
print("Insufficient funds")
return false
}
this.balance -= amount
return true
}
fn check() -> Float {
return this.balance
}
}
var account = BankAccount(1000.0)
account.deposit(500.0)
print(account.check()) // 1500
account.withdraw(200.0)
print(account.check()) // 1300
Inheritance¶
Use : to inherit from another class:
class Animal(var name: String) {
fn speak() {
print(this.name + " makes a sound")
}
}
class Dog(var name: String, var breed: String) : Animal(name) {
fn speak() {
print(this.name + " barks")
}
}
class Cat(var name: String) : Animal(name) {
fn speak() {
print(this.name + " meows")
}
}
var fido = Dog("Fido", "Labrador")
var michi = Cat("Michi")
fido.speak() // Fido barks
michi.speak() // Michi meows
The super Keyword¶
super allows accessing parent class methods:
In the Constructor¶
class Vehicle(var brand: String, var model: String) {
fn describe() -> String {
return this.brand + " " + this.model
}
}
class Car(var brand: String, var model: String, var doors: Int) : Vehicle(brand, model) {
fn describe() -> String {
var base = super.describe() // Call parent method
return base + " (" + str(this.doors) + " doors)"
}
}
var car = Car("Toyota", "Corolla", 4)
print(car.describe()) // Toyota Corolla (4 doors)
In Methods¶
class Employee(var name: String, var salary: Float) {
fn calculateBonus() -> Float {
return this.salary * 0.10
}
}
class Manager(var name: String, var salary: Float, var department: String) : Employee(name, salary) {
fn calculateBonus() -> Float {
var baseBonus = super.calculateBonus() // 10%
return baseBonus * 2 // Managers: 20%
}
}
var emp = Employee("Ana", 30000.0)
var mgr = Manager("Carlos", 50000.0, "Sales")
print(emp.calculateBonus()) // 3000
print(mgr.calculateBonus()) // 10000
Polymorphism¶
Objects of different classes can be treated uniformly:
class Shape {
fn area() -> Float {
return 0.0
}
}
class Square(var side: Float) : Shape {
fn area() -> Float {
return this.side * this.side
}
}
class Circle(var radius: Float) : Shape {
fn area() -> Float {
return 3.14159 * this.radius * this.radius
}
}
// Function that works with any shape
fn totalArea(shapes: List[Shape]) -> Float {
var total = 0.0
for shape in shapes {
total += shape.area()
}
return total
}
var shapes = [
Square(5.0),
Circle(3.0)
]
print(totalArea(shapes)) // 53.27431
Methods with Short Syntax¶
You can use single-expression syntax in methods:
class Math {
fn square(x: Int) -> Int = x * x
fn cube(x: Int) -> Int = x * x * x
fn double(x: Int) -> Int = x * 2
fn average(a: Int, b: Int) -> Int = (a + b) / 2
}
var m = Math()
print(m.square(4)) // 16
print(m.cube(3)) // 27
print(m.average(10, 20)) // 15
Common Patterns¶
Builder¶
class PersonBuilder(var name: String = "", var age: Int = 0, var city: String = "") {
fn withName(name: String) -> PersonBuilder {
this.name = name
return this
}
fn withAge(age: Int) -> PersonBuilder {
this.age = age
return this
}
fn withCity(city: String) -> PersonBuilder {
this.city = city
return this
}
fn build() -> Dict[String, Any] {
return {
"name": this.name,
"age": this.age,
"city": this.city
}
}
}
var person = PersonBuilder()
.withName("Ana")
.withAge(25)
.withCity("Madrid")
.build()
print(person)
Composition¶
class Engine(var power: Int, var running: Bool = false) {
fn start() {
this.running = true
print("Engine started")
}
fn stop() {
this.running = false
print("Engine stopped")
}
}
class Car {
constructor(brand: String, enginePower: Int) {
this.brand = brand
this.engine = Engine(enginePower) // Composition
}
fn start() {
print("Starting " + this.brand)
this.engine.start()
}
fn stop() {
this.engine.stop()
print(this.brand + " stopped")
}
}
var car = Car("Toyota", 150)
car.start()
// Starting Toyota
// Engine started
Traits¶
Traits define shared behavior that classes can implement. They're similar to interfaces but can have default implementations.
Defining a Trait¶
trait Describable {
fn describe() -> String
}
Implementing a Trait¶
Use : to implement traits (same syntax as inheritance):
class Person(var name: String, var age: Int) : Describable {
fn describe() -> String {
return "Person: " + this.name + ", " + str(this.age) + " years old"
}
}
var p = Person("Ana", 25)
print(p.describe()) // Person: Ana, 25 years old
Default Implementations¶
Traits can provide default implementations:
trait Greetable {
fn greet() {
print("Hello, I am " + this.name)
}
}
class Student(var name: String) : Greetable {
// Uses default greet() implementation
}
var s = Student("Luis")
s.greet() // Hello, I am Luis
Trait Inheritance¶
Traits can extend other traits:
trait Walkable {
fn walk()
}
trait Runner : Walkable {
fn run()
}
// Classes implementing Runner must implement both walk() and run()
class Athlete(var name: String) : Runner {
fn walk() {
print(this.name + " is walking")
}
fn run() {
print(this.name + " is running")
}
}
Multiple Traits¶
Classes can implement multiple traits:
trait Printable {
fn toPrint() -> String
}
trait Comparable {
fn compare(other: Any) -> Int
}
class Value(var n: Int) : Printable, Comparable {
fn toPrint() -> String {
return "Value(" + str(this.n) + ")"
}
fn compare(other: Any) -> Int {
return this.n - other.n
}
}
Class Inheritance with Traits¶
When inheriting from a class and implementing traits, the class comes first:
class Animal(var name: String) {
fn speak() {
print(this.name + " makes a sound")
}
}
trait Flyable {
fn fly()
}
class Bird(var name: String) : Animal(name), Flyable {
fn fly() {
print(this.name + " is flying")
}
}
var b = Bird("Tweety")
b.speak() // Tweety makes a sound
b.fly() // Tweety is flying