# Modular Programming ## 1. Introduction - A. What is Object-Oriented Programming? - B. Why use OOP? (vs. procedural) - C. Real-world analogies (e.g., modeling components like pumps, motors, or vehicles) ## 2. Core OOP Concepts - A. **Classes and Objects** - Definitions - Syntax in Python - B. **Attributes and Methods** - Instance variables - Functions inside classes - C. **Encapsulation** - Public vs private variables - Using `__init__` and `self` - D. **Inheritance** - Parent and child classes - Reuse and extension of code - E. **Polymorphism** _(brief overview)_ - Method overriding - Flexibility in interfaces ## 3. Python OOP Syntax and Examples - A. Define a simple class (e.g., `Spring`) - B. Instantiate objects and use methods - C. Show `__init__`, `__str__`, custom methods - D. Add a derived class (e.g., `DampedSpring` inherits from `Spring`) ## 4. Engineering Applications of OOP - A. Modeling a mechanical system using classes - Example: Mass-Spring-Damper system - B. Creating reusable components (e.g., `Material`, `Beam`, `Force`) - C. Organizing simulation code with OOP ## 5. Hands-On Coding Activity - A. Write a class for a basic physical component (e.g., `Motor`) - B. Add behavior (e.g., `calculate_torque`) - C. Extend with inheritance (e.g., `ServoMotor`) - D. Bonus: Integrate two objects to simulate interaction --- # Background To understand what modular programming is and why using it, let's take a look a the origin of computer code. # Traditional Programming Procedural Oriented Programming. | OOP | POP | | ---------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------- | | [Object oriented](https://www.geeksforgeeks.org/dsa/introduction-of-object-oriented-programming/). | [Structure oriented](https://www.geeksforgeeks.org/computer-networks/difference-between-structured-programming-and-object-oriented-programming/). | | Program is divided into objects. | Program is divided into functions. | | Bottom-up approach. | Top-down approach. | | Inheritance property is used. | Inheritance is not allowed. | | Encapsulation is used to hide the data. | No data hiding. | | Concept of virtual function. | No virtual function. | | Object functions are linked through message passing. | Parts of program are linked through parameter passing. | | Adding new data and functions is easy | Expanding new data and functions is not easy. | | The existing code can be reused. | No code reusability. | | use for solving big problems. | Not suitable for solving big problems. | | Python, [C++](https://www.geeksforgeeks.org/cpp/c-plus-plus/), [Java](https://www.geeksforgeeks.org/java/java/). | [C](https://www.geeksforgeeks.org/c/c-programming-language/), Pascal. | https://www.geeksforgeeks.org/cpp/difference-between-oop-and-pop/ ## POP vs. OOP # Modular Programming Modular programming or better known as Object-Oriented Programming (OOP) is a way we can structure programs so that properties and behaviors of objects are grouped together. It allows us to re-use code which simplifies the code for better readability in larger programs and reduces the potential for bugs. You're probably familiar with the saying "Don't re-invent the wheel". OOP is the programmers solution to this. Python allows us to import libraries so that we don't have to write everything from scratch. The libraries used in this course provide us with algorithms to calculate a solution, structure data and plot data. When looking at the source code of these library you may see keywords, such as `class`. This tutorial will delve into the fundamental concepts of OOP. #### Classes One analogy for OOP goes as follows. Imagine you are an urban planner in the town of Wellington, the town is rapidly expanding and housing is in high demand. You are tasked with developing a new housing area. Designing 50 different houses will take too much, but building the same house 50 times will make the neighborhood look like a subdivision from the 1950s. Your solution may be to come up with some structure that represents a house, this is our `class`. This structure may define how a house should look like, for example, a house should have doors, rooms and windows. Even though every house won't looks that same, they all will follow the same patterns. Properties such as color, window types, garage type can be introduced inside the `class` however it will not be define until we build the specific house. #### Objects Once we have the general structure of what a house is going to look like, we can use this blueprint to start building houses faster. With this blueprint defined. We can start building houses from the blueprint. In python we call this *initializing objects*. Where an actual house built from this blueprint with specific attributes (e.g. house #305, blue house, single-hung window, attached garage) is referred to as an `object`. >[!NOTE] Difference between functions and objects > **Functions:** A procedure. Such as a force. > **Objects:** Include properties. Such as a spring, with a defined spring constant. #### Example Let's take a look at an example. Of how this looks like in python. Do not worry if you don't understand the syntax below, we will explain this later. Let's define the class: ```python class House(): def __init__(self, house_number, color, garage_type): self.house_number = house_number self.color = color self.garage_type = garage_type def describe(self): print("House #" + self.house_number + " is painted " + self.color) print("and has a " + self.garage_type + " garage.") ``` Now we can use our class to define new objects: ```python house1 = House(house_number=101, color='blue', garage_type='attached') house1.describe() house2 = House(house_number=102, color='green', garage_type='detached') house2.describe() ``` In the example above, we first defined the class `House` with `house_number`, `color`, and `garage_type` as the data (attributes), and `describe()` as the method. Once we initialized objects such as `house1` and `house2`, we can see that the class provides the overall blueprint, while each object is an individual house built from that blueprint. It is clear that `house1` and `house2` are completely independent from one another, even though they were both created using the same `House` class. >[!NOTE] Attributes > Data that is stored within an object. >[!NOTE] Methods >A function that lives inside a class and has access to the data stored inside the object. usually ### Why use it? When you start to represent more complex data primitive data structures, such as arrays become more difficult to manage. Let's consider you're trying to track employees in an organization. You may need to store name, age, position and year of recruitment. One way to represent this would be as follows: ```python kirk = ["James Kirk", 34, "Captain", 2265] spock = ["Spock", 35, "Science Officer", 2254] mccoy = ["Leonard McCoy", "Chief Medical Officer", 2266] ``` There are a number of issues with this approach. First, it can make larger code files more difficult to manage. If you reference `kirk[0]` several lines away from where you declared the `kirk` list, will you remember that the element with index `0` is the employee’s name? Second, it can introduce errors if employees don’t have the same number of elements in their respective lists. In the `mccoy` list above, the age is missing, so `mccoy[1]` will return `"Chief Medical Officer"` instead of Dr. McCoy’s age. The modularity of the programs allows us to re-use code which reduces the number of potential bugs. #### Four Pillars of OOP Now that we know how classes and objects look like. Let's take a look at the four fundamental concepts in OOP. The four pillars: 1. Inheritance - enables the creation of hierarchical relationships between classes, allowing a subclass to inherit attributes and methods from a parent class. This promotes code reuse and reduces duplication. 2. Encapsulation - allows you to bundle data (attributes) and behaviors (methods) within a class to create a cohesive unit. By defining methods to control access to attributes and its modification, encapsulation helps maintain data integrity and promotes modular, secure code. 3. Polymorphism - allows you to treat objects of different types as instances of the same base type, as long as they implement a common interface or behavior. Python’s duck typing make it especially suited for polymorphism, as it allows you to access attributes and methods on objects without needing to worry about their actual class. 4. Abstraction - focuses on hiding implementation details and exposing only the essential functionality of an object. By enforcing a consistent interface, abstraction simplifies interactions with objects, allowing developers to focus on what an object does rather than how it achieves its functionality. ## Examples: #### Modelling physical systems We can model components as classes where we specify it's properties as attributes and behavior as methods. Use the provided equations to calculate the torque a) Using the equations below, model the component as a class. $$ \tau(I) = K_t * I \tag{} $$ $\tau$ - is the torque produced by the motor $K_t$ - torque constant $I$ - current flowing through the motor $$ I(f) = \frac{V - K_t * f}{R} \tag{} $$ $V$ -> Voltage provided to the circuit $K_t$ -> torque constant $f$ -> motor speed (rev/s or Hz) $R$ -> Resistance of the motor ```python class Motor: def __init__(self, torque_constant, resistance, voltage): self.Kt = torque_constant self.R = resistance self.V = voltage def current(self, speed): return (self.V - self.Kt * speed) / self.R def torque(self, speed): return self.Kt * self.current(speed) ``` b) You've selected 3 candidates to power an electrical powered car. with the specs given in the table below. The motor you're given is spec'ed with a torque constant $0.05 Nm/A$, resistance in the motor of $2.0 \Omega$ which is powered by battery of $12V$. | Motor | $K_t$ | $R$ | Battery | | ----- | ----- | ---- | ------- | | 1 | 0.05 | 2 | 12V | | 2 | 0.05 | 2 | 24V | | 3 | 0.1 | 1.75 | 12V | ```python motor1 = Motor(0.05, 2.0, 12) motor2 = Motor(0.05, 2.0, 12) motor3 = Motor(0.05, 2.0, 12) print(motor1.torque(100)) # torque at 100 rad/s ``` #### Simulation Frameworks Use of *inheritance* and *composition*. ```python class PropulsionSystem: def __init__(self, mass_flow_rate): self.mass_flow_rate = mass_flow_rate def thrust(self, exhaust_velocity): return self.mass_flow_rate * exhaust_velocity class RocketEngine(PropulsionSystem): def __init__(self, mass_flow_rate, chamber_pressure, area_ratio): super().__init__(mass_flow_rate) self.chamber_pressure = chamber_pressure self.area_ratio = area_ratio def efficiency(self): return 0.98 # simplified example ``` #### Data Processing Pipeline ```python class DataLogger: def __init__(self, filepath): import pandas as pd self.df = pd.read_csv(filepath) def filter_noise(self): self.df['filtered'] = self.df['signal'].rolling(5).mean() def plot(self): self.df[['signal', 'filtered']].plot() ``` #### Simulation of a mass-spring-damper system Model a spring-mass-damper system using classes and ```python class Spring: def __init__(self, spring_constant) self.K = spring_constant self. ``` - B. Creating reusable components (e.g., `Material`, `Beam`, `Force`) https://github.com/enesdemirag/programming-exercises/blob/master/exercises/mass-spring-damper-simulation.md ### Summary ```mermaid flowchart TD A[OOP] --> B[Class] B --> C["Attributes (variables and data)"] B --> D["Methods (functions and behaviors)"] C -->|define structure| E[Class Definition] D -->|define behavior| E E -->|instantiation| F[Object] F --> G[Object has its own
attribute values] F --> H[Object can call methods] ``` --- Starting a class is done with the `class` keyword followed by the name of the class. ```python class Dog: pass ``` In this example `pass` is used a placeholder to indicate where code will eventually go. This prevents python to output any errors. Next, give our class some properties and behaviors. For the purpose of this demonstration we will keep it small but in reality the class may be significantly more detailed. The properties are defined inside the method called `.__init__()`. It runs automatically when a new object of a class is created. It's main purpose is to initialize the object attributes and set up its initial state. When an object is created, memory is allocated for it, and `__init__` helps organize that memory by assigning values to attributes. Let's add a name, breed and age attibute to our ```python class Dog: def __init__(self, name, breed="Mixed", age=1): self.name = name self.breed = breed self.age = age dog1 = Dog("Buddy") dog2 = Dog("Bolt", "German Sheppard", 5) print(dog1.name, dog1.breed, dog1.age) print(dog2.name, dog2.breed, dog2.age) ``` Notice how the `__init__` method defaults the inputs`breed="Mixed"` and `age=1` when no parameters are given. ```python ``` ```python ``` ```python ``` ```python ``` https://eng.libretexts.org/Courses/Arkansas_Tech_University/Engineering_Modeling_and_Analysis_with_Python/01%3A_Introduction_to_Engineering_Modeling_and_Analysis_with_Python