Classes
Classes#
A class is a blueprint that defines the structure and behavior of objects. It specifies what data objects will hold (attributes) and what operations they can perform (methods). Classes enable abstraction and code reusability by allowing you to define common structure and behavior once, then create multiple instances that share these characteristics.
Think of a class like a guitar factory blueprint—it defines the manufacturing process, materials, and specifications, but each guitar (object) that comes off the production line can have different finishes, colors, or customizations while maintaining the core design.
Class Structure:
graph TD
A[Class Blueprint] --> B[Properties/Attributes]
A --> C[Methods/Behaviors]
A --> D[Constructor]
B --> E[brand: String]
B --> F[model: String]
B --> G[numberOfStrings: int]
C --> H["playChord()"]
C --> I["tune()"]
C --> J["changeStrings()"]
D --> K[Initialize Object]
style A fill:#e3f2fd
Class-Object Relationship:
graph LR
A[Class] -->|Creates| B[Object Instance 1]
A -->|Creates| C[Object Instance 2]
A -->|Creates| D[Object Instance 3]
style A fill:#e3f2fd
style B fill:#c8e6c9
style C fill:#c8e6c9
style D fill:#c8e6c9
Let's explore how different programming languages implement classes:
Constructor Overloading#
Constructor overloading allows a class to have multiple constructors with different parameter lists, providing flexibility in object initialization.
public class Guitar {
private String brand;
private String model;
public Guitar(String brand, String model) {
this.brand = brand;
this.model = model;
}
public Guitar(String brand) {
this.brand = brand;
this.model = "Standard Model";
}
}
Guitar guitar1 = new Guitar("Fender", "Stratocaster");
Guitar guitar2 = new Guitar("Gibson");
class Guitar {
var brand: String
var model: String
init(brand: String, model: String) {
self.brand = brand
self.model = model
}
convenience init(brand: String) {
self.init(brand: brand, model: "Standard Model")
}
}
let guitar1 = Guitar(brand: "Fender", model: "Stratocaster")
let guitar2 = Guitar(brand: "Gibson")
Class Attributes#
Class attributes (also called instance variables or properties) store the data that defines the state of each object. They represent the characteristics that make each instance unique while sharing the same structure defined by the class.
Access Modifiers#
Access modifiers control where attributes can be accessed from, implementing the principle of encapsulation—one of the core OOP principles. They help protect data integrity and control how the class interacts with the outside world.
-
private: Accessible only within the same class. This is the most restrictive level and is commonly used for internal data that shouldn't be directly modified from outside the class. -
public: Accessible from anywhere in the program. This is the default visibility in most languages and is used for data that needs to be freely accessible. -
protected: Accessible within the class and by its subclasses (inheritance). This provides a middle ground for data that subclasses need access to but shouldn't be public.
Why Use Access Modifiers?#
- Data Protection: Prevents external code from accidentally corrupting object state
- Interface Control: Defines what parts of your class are meant for external use
- Maintainability: Makes it easier to change internal implementation without breaking external code
- Design Clarity: Makes the intended usage of class members explicit
To better illustrate class attributes (sometimes called instance variables or properties) and the use of access modifiers for encapsulation, here are equivalent examples for each language using the same features:
- Private data members: The
brandandmodelattributes are private (where supported) to encapsulate the data. - Public getters: Controlled access to private attributes via getter methods or properties.
- Private helper method: A simple method (
isValidBrand) to show an internal/helper method. - Constructor: Public or default constructor to initialize the attributes.
public class Guitar {
// Private attributes - only accessible within this class
private String brand;
private String model;
// Public constructor
public Guitar(String brand, String model) {
this.brand = brand;
this.model = model;
}
// Public getter methods
public String getBrand() { return brand; }
public String getModel() { return model; }
// Private helper method
private boolean isValidBrand(String brand) {
return brand != null && !brand.isEmpty();
}
}
class Guitar {
// Private attributes
private brand: string;
private model: string;
// Public constructor
constructor(brand: string, model: string) {
this.brand = brand;
this.model = model;
}
// Public getter methods
getBrand(): string {
return this.brand;
}
getModel(): string {
return this.model;
}
// Private helper method
private isValidBrand(brand: string): boolean {
return brand !== null && brand !== "";
}
}
class Guitar {
// Private attributes (Dart uses _ to mark fields private to library)
String _brand;
String _model;
// Public constructor
Guitar(this._brand, this._model);
// Public getter methods
String get brand => _brand;
String get model => _model;
// Private helper method
bool _isValidBrand(String brand) {
return brand.isNotEmpty;
}
}
class Guitar {
// Private properties
private var brand: String
private var model: String
// Public initializer
init(brand: String, model: String) {
self.brand = brand
self.model = model
}
// Public getter computed properties
var getBrand: String { return brand }
var getModel: String { return model }
// Private helper method
private func isValidBrand(_ brand: String) -> Bool {
return !brand.isEmpty
}
}
class Guitar:
def __init__(self, brand, model):
self.__brand = brand # double underscore for name mangling (pseudo-private)
self.__model = model
# Public getter methods
@property
def brand(self):
return self.__brand
@property
def model(self):
return self.__model
# Private helper method
def __is_valid_brand(self, brand):
return isinstance(brand, str) and len(brand) > 0
Static Attributes#
Static attributes belong to the class itself, not to any instance. They're shared across all instances.
Class Methods#
Methods define the behavior of a class and can access instance attributes.
Static Methods#
Static methods belong to the class itself and can be called without creating an instance. They're useful for utility functions and factory methods.
String Representation#
String representation methods convert objects to human-readable strings for debugging and display.
For comprehensive details, see String Representation.
public class Guitar {
private String brand;
private String model;
public Guitar(String brand, String model) {
this.brand = brand;
this.model = model;
}
@Override
public String toString() {
return brand + " " + model;
}
}
Guitar guitar = new Guitar("Fender", "Stratocaster");
System.out.println(guitar); // Output: Fender Stratocaster
class Guitar {
brand: string;
model: string;
constructor(brand: string, model: string) {
this.brand = brand;
this.model = model;
}
toString(): string {
return `${this.brand} ${this.model}`;
}
}
const guitar = new Guitar("Fender", "Stratocaster");
console.log(guitar.toString()); // Output: Fender Stratocaster
Type Checking#
Type checking allows you to determine if an object is an instance of a specific class or subclass.