What is Data Abstraction?

Data Abstraction is hiding the complex inner workings of code, and only exposing the part that the user/coder needs to know.

Real Life Examples

Some real life examples of Data Abstraction include cars, mobile apps, games, and even the computer that you are using!!

Car example

Cars are a real life example of Data Abstraction with their steering wheel. The inner electronics of the system is hidden, and only the steering wheel, and the option of moving the car left and right are shown

Mobile App example

Mobile App’s are another great example for Data Abstraction. One example of this is the YouTube app. Youtube hides the complex hosting features that is use, and only shows you the actual video. Another example of Data abstraction would be the messages app on your phone. It hides the complex innerworkings of how it is being sent to someone elses phone, and only shows if it is sent or not.

Games example

In games such as Minecraft as Pokemon Data abstraction is a necessary, as you need to hide how multiple things are being done in order to simplify the user experience

Relevant examples in Javascript

Some examples of Data abstraction in Javascript include classes, function and objects. We will go more in depth into how these are examples

Built in functions

Built in Javascript functions are a great example of Data Abstraction. The method to create them is already defined, the user simply has to call it. For example Math.cos() is a built in function that takes a number as an argument and returns the cosine of that number. The user does not need to know how the function works, they only need to know that it takes a number as an argument and returns the cosine of that number.

%%js 
let angleInDegrees = 57;
let angleInRadians = angleInDegrees * (Math.PI / 180);
// 11 lines of code to calculate the cosine of an angle without using built in operators
    function factorial(n) {
        let result = 1;
        for (let i = 2; i <= n; i++) { // Start at 2, as multiplying by 1 is unnecessary
            result *= i;
        }
        return result;
    }
function cosine(x) {
    return 1 - x**2 / factorial(2) + x**4 / factorial(4) - x**6 / factorial(6) + x**8 / factorial(8) - x**10 / factorial(10);
}
let createdOutput = cosine(angleInRadians);

// 1 line of code to calculate the cosine of an angle using built in operators
let inBuiltOutput = Math.cos(angleInRadians);
console.log("My created function: " + createdOutput);
console.log("Original function: " + inBuiltOutput);
console.log("You can also see that my function is less accurate by a little bit")

<IPython.core.display.Javascript object>

Functions as Data Abstractions

Functions are a great example of data abstraction because they help us simplify complex tasks. Here’s how they do that:

  1. Simplification: Functions take a set of instructions and bundle them together. This way, we don’t have to think about all the details every time we want to use them.

  2. Reusability: Once we create a function, we can use it multiple times throughout our code. This saves time and effort since we don’t need to rewrite the same steps.

  3. Clarity: By giving functions clear names, we can easily understand what they do. This makes our code easier to read and follow.

In short, functions allow us to focus on the bigger picture without getting lost in the details, making programming simpler and more organized.

Below is an example of a reusable function that can add 2 numbers together

%%html
    <div>
        <p>Please put your first number here</p>
        <input type="number" id="num1" name="num1">
        <p>Please put your second number</p>
        <input type="number" id="num2" name="num2">
        <br>
        <br>
        <button onclick="addNumbers('num1', 'num2', 'output')">Submit</button>
    </div>
    <p id="output"></p>
    <div>
        <p>Please put your first number here</p>
        <input type="number" id="number1" name="number1">
        <p>Please put your second number</p>
        <input type="number" id="number2" name="number2">
        <br>
        <br>
        <button onclick="addNumbers('number1', 'number2', 'output2')">Submit</button>
    </div>
    <p id="output2"></p>
    <script>
        function addNumbers(id1, id2, output) {
            let num1 = parseFloat(document.getElementById(id1).value);
            let num2 = parseFloat(document.getElementById(id2).value);
            let solution = num1 + num2;
            document.getElementById(output).innerHTML = solution;
        }
    </script>

Please put your first number here

Please put your second number



Please put your first number here

Please put your second number



Classes as Data Abstractions

Classes play a crucial role in data abstraction in programming. Here’s how they achieve this:

  1. Reusability and Inheritance:
    Once a class is defined, it can be reused to create multiple instances (objects) without rewriting code. Through inheritance, new classes can inherit properties and methods from existing ones, promoting code reuse and reducing redundancy.

  2. Polymorphism:
    This allows objects of different classes to be treated as objects of a common superclass. It enables more flexible and generic code that can operate on different types of objects, enhancing code extensibility.

In summary, classes help create organized, reusable, and understandable code by encapsulating data and behavior, abstracting complexity, and supporting a robust inheritance structure.

What is inheritance?

Inheritance allows a new class to contain properties from another class using the word extends. Below is an example of 2 classes, one being a parent class and the other being a child class. The child class inherits the properties of the parent class such as its parameters and functions

%%js
    // Define the Phone class
    class Phone {
        constructor(model, specs) {
            this.model = model;
            this.specs = specs;
        }
        
        listSpecs(id) {
            document.getElementById(id).innerHTML = ("The " + this.model + " weighs " + this.specs.weight + 
                        ", has a display of " + this.specs.display + 
                        " and has a " + this.specs.chip);
            console.log("The " + this.model + " weighs " + this.specs.weight + 
                        ", has a display of " + this.specs.display + 
                        " and has a " + this.specs.chip);
        }
    }

    // Define the iPhone16 class
    class iPhone16 extends Phone {
        constructor() {
            const model = "iPhone 16"; // Use const or let here
            const specs = {
                weight: "170 grams",
                display: "6.1 inches",
                chip: "A18"
            };
            super(model, specs); // Call the parent constructor
        }
    }
        let myiPhone;
        if (typeof myiPhone === 'undefined') {
            // Create an instance of iPhone16 and call the listSpecs method
            myiPhone = new iPhone16(); // Create an instance of iPhone16
        }
        myiPhone.listSpecs("iPhone"); // Call the listSpecs method
<IPython.core.display.Javascript object>
%%html
    <p> These are the specs of the iPhone 16:</p>
    <p id="iPhone"></p>

These are the specs of the iPhone 16:

How to use Super in Data Abstraction

Super is a function that calls the Parent class. For example, in the example below I use super to access the already initialiazed variables from the parent class. You can also use super to update a function inside the parent class. For example, in the getPrice function I use super to access the getPrice function from the parent class and then multiply it by the size of the bagel to get the price. While on a simple function like this is may seem trivial to just rewrite the getPrice function, it is very useful when you have a more complex function that you want to update. Super is useful as if you know what it outputs, you can use it instead of rewriting the code.

%%js
    class Product{
        constructor(price,size,taxRate) {
            this.price = price;
            this.size = size;
            this.taxRate = taxRate;
        }   
        getPrice() {
            return this.price + this.taxRate * this.price;
        }
        product(){
            const className = this.constructor.name.toLowerCase();
            return "You are ordering a " + className + " with a price of " + this.getPrice() + " dollars, a size of " + this.size;
        }
    }
    class Bagel extends Product{
        constructor(price, size, taxRate, flavor) {
            super(price, size, taxRate);
            this.flavor = flavor;
        }
        getPrice(){
            return super.getPrice() * this.size;
        }
        product(){
            return super.product() + " and a flavor of " + this.flavor;
        }
    }
const sesameBagel = new Bagel(1.5, 2, 0.07, "sesame");
console.log(sesameBagel.product());
<IPython.core.display.Javascript object>

Polymorphism

Polymorphism is a fundamental concept in object-oriented programming that allows methods to do different things based on the object that is calling them. It enables objects of different classes to be treated as objects of a common superclass.

Example

A base class Shape might have a method area(). Subclasses like Circle and Square provide their specific implementations. When area() is called, the correct version is executed based on the object type.

Benefits

  • Code Reusability: Use the same interface for different types.
  • Flexibility: Easily extend and manage code.
%%js
    class RegularShape {
        constructor(sideLength, apothem){
            this.sideLength = sideLength;
            this.apothem = apothem;
        }
        area(){
            throw new Error("This method must be overridden!");
        }
        perimeter(){
            throw new Error("This method must be overridden!");
        }
    }

    class Square extends RegularShape {
        constructor(sideLength){
            super(sideLength, sideLength/2);
        }
        area(){
            return this.sideLength ** 2;
        }
        perimeter(){
            return this.sideLength * 4;
        }
    }

    class RegularPentagon extends RegularShape{
        constructor(sideLength){
            super(sideLength, sideLength/2*Math.tan(Math.PI/5));
        }
        area(){
            return 5 * this.sideLength * this.apothem / 2;
        }
        perimeter(){
            return this.sideLength * 5;
        }
    }
    class Arc extends RegularShape{
        constructor(radius, degrees){
            // Using sideLength for the arc length of an arc in degrees
            super(radius, radius);
            this.radius = radius;
            this.arcLength = 2 * Math.PI * radius * degrees / 360;
            if(degrees >0 && degrees <= 360){
                this.degrees = degrees;
            }
            else{
                throw new Error("Degrees must be between 0 and 360");
            }
        }
        area(){
            return Math.PI * this.radius ** 2 * this.degrees/360;
        }
        perimeter(){
            return this.arcLength + 2 * this.radius;
        }
    }

    let pentagon = new RegularPentagon(9);
    let square = new Square(5);
    let arc = new Arc(1,360 )
    console.log("Area of the pentagon: " + pentagon.area());
    console.log("Perimeter of the pentagon: " + pentagon.perimeter());
    console.log("Area of the Square: "+ square.area());
    console.log("Perimeter of the Square: "+ square.perimeter());
    console.log("Area of the arc: " + arc.area());
    console.log("Perimeter of the arc: " + arc.perimeter());
<IPython.core.display.Javascript object>