Friday, February 24, 2023

Observer Pattern

The observer pattern is a design pattern that allows objects to observe changes in the state of another object, and to receive notifications when those changes occur. Here's an example of how to implement the observer pattern in C++:

#include <iostream> #include <vector> class Observer {     public: virtual ~Observer()     {     } virtual void update(int data) = 0; }; class Subject {     public: void attach(Observer* observer)     { observers.push_back(observer); } void detach(Observer* observer)     { for (auto it = observers.begin(); it != observers.end(); ++it) { if (*it == observer)     { observers.erase(it); break; } } } void notify(int data)     { for (auto observer : observers) { observer->update(data); } }     private: std::vector<Observer*> observers; }; class ConcreteObserver : public Observer {     public: ConcreteObserver(const std::string& name) : name(name)     {     } void update(int data) override     { std::cout << "Observer " << name << " received update: " << data << std::endl; }     private: std::string name; }; int main() { Subject subject; ConcreteObserver observer1("Observer 1"); ConcreteObserver observer2("Observer 2"); ConcreteObserver observer3("Observer 3"); subject.attach(&observer1); subject.attach(&observer2); subject.attach(&observer3); subject.notify(42); subject.detach(&observer2); subject.notify(123); return 0; }

In this example, we have a Subject class that maintains a list of observers, which are objects that implement the Observer interface. The Subject class has methods for attaching and detaching observers, as well as a notify method that sends a notification to all attached observers.

The ConcreteObserver class is an implementation of the Observer interface. It simply prints out a message when it receives an update from the subject.

In the main function, we create an Subject object and three ConcreteObserver objects. We attach all three observers to the subject and then send a notification to all observers by calling the notify method on the subject. We then detach the second observer and send another notification to the remaining two observers.

Dependency Injection


Dependency Injection (DI) is a technique where an object's dependencies are provided to it, rather than the object creating or finding them on its own.

Constructor injection

Constructor injection is a design pattern used in object-oriented programming to decouple dependencies between classes. It involves injecting dependencies into a class through its constructor. This allows for better control over the initialization of the class and provides better testability.

Here's an example C++ code that demonstrates constructor injection:


#include <iostream>
#include <memory>

class ILogger 
{
public:
    virtual void log(const std::string& message) = 0;
    virtual ~ILogger() = default;
};

class ConsoleLogger: public ILogger 
{
public:
    void log(const std::string& message) override 
    {
        std::cout << message << std::endl;
    }
};

class User 
{
private:
    std::shared_ptr<ILogger> logger_;

public:
    User(const std::shared_ptr<ILogger>& logger) : logger_(logger) 
    {

    }

    void performAction() 
    {
        logger_->log("User is performing an action");
    }
};

int main() 
{
    auto logger = std::make_shared<ConsoleLogger>();
    User user(logger);
    user.performAction();
    return 0;
}

In this example, the User the class has a dependency on a logger, which is injected into the class through its constructor. The constructor takes an instance of the ILogger interface as a parameter, allowing the class to work with any implementation of the logger that adheres to the required contract.

In the main function, a ConsoleLogger instance is created and passed to the User constructor. The User instance then uses the logger to log a message when it performs an action. If we wanted to use a different logger implementation, we could simply create a new class that implements the ILogger interface and pass it to the User constructor. This allows for more flexibility in configuring the dependencies of the User class, and it ensures that the dependencies are properly initialized before the class is used.


Setter injection


Setter injection is a design pattern used in object-oriented programming to decouple dependencies between classes. It involves injecting dependencies into a class through setter methods instead of the constructor. This allows for more flexibility and easier testing, as dependencies can be swapped out at runtime.

Here's an example C++ code that demonstrates setter injection:

#include <iostream> #include <memory> class ILogger { public: virtual void log(const std::string& message) = 0; virtual ~ILogger() = default; }; class ConsoleLogger : public ILogger { public: void log(const std::string& message) override     { std::cout << message << std::endl; } }; class User { private: std::shared_ptr<ILogger> logger_; public: User() { } void setLogger(const std::shared_ptr<ILogger>& logger) { logger_ = logger; } void performAction() { logger_->log("User is performing an action"); } }; int main() { auto logger = std::make_shared<ConsoleLogger>(); User user; user.setLogger(logger); user.performAction(); return 0; }

In this example, the User the class has a dependency on a logger, which is injected into the class through a setter method called setLogger. Instead of passing the logger instance through the constructor, we can create the User instance first and then set the logger later using the setLogger method.

In the main function, an ConsoleLogger instance is created and passed to the User instance using the setLogger method. The User the instance then uses the logger to log a message when it performs an action. If we wanted to use a different logger implementation, we could simply create a new class that implements the ILogger interface and pass it to the setLogger method at runtime. This allows for more flexibility in configuring the dependencies of the User class.

IInterface injection

Interface injection

Interface injection is a design pattern used in object-oriented programming to decouple dependencies between classes. It involves defining an interface that defines the contract of the functionality required by a dependent class and then injecting that interface into the dependent class, instead of injecting a concrete implementation of the required functionality. This allows the dependent class to be more flexible and reusable, as it can work with any implementation of the interface that adheres to the required contract.

Here's an example C++ code that demonstrates interface injection:

#include <iostream> #include <memory> class ILogger { public: virtual void log(const std::string& message) = 0; virtual ~ILogger() = default; }; class ConsoleLogger : public ILogger { public: void log(const std::string& message) override     { std::cout << message << std::endl; } }; class User { private: std::shared_ptr<ILogger> logger_; public: User(const std::shared_ptr<ILogger>& logger) : logger_(logger)     {     } void performAction()     { logger_->log("User is performing an action"); } }; int main() { auto logger = std::make_shared<ConsoleLogger>(); User user(logger); user.performAction(); return 0; } 

In this example, there are two classes: ILogger and ConsoleLogger. ILogger is an interface that defines the contract for a logger, which is simply a log function that takes a string message as input. ConsoleLogger is a concrete implementation of the ILogger interface that logs messages to the console.

The User the class has a dependency on a logger, which is injected into the class via its constructor. Instead of injecting a concrete implementation of the logger, the constructor takes an instance of the ILogger interface, allowing the class to work with any implementation of the logger that adheres to the required contract.

In the main function, an ConsoleLogger instance is created and passed to the User constructor. The User the instance then uses the logger to log a message when it performs an action. If we wanted to use a different logger implementation, we could simply create a new class that implements the ILogger interface and pass it to the User constructor.

Pointer to Implementation (PIMPL) idiom


The Pointer to Implementation (PIMPL) idiom is a technique used in C++ to separate the implementation details of a class from its public interface. 

It is used to reduce the compilation dependencies of a class, improve encapsulation, and hide implementation details from the users of the class.

The PIMPL idiom is implemented by creating a private member of the class that is a pointer to an opaque implementation class. The implementation class is defined in the implementation file, and the public class has a public interface that uses the implementation class through the private pointer.

Here's an example of how you might use the PIMPL idiom to implement a simple class:



    // MyClass.h
    class MyClass
    {
    public:
        MyClass();
        ~MyClass();
        void doSomething();

    private:
        class MyClassImpl;
        MyClassImpl* impl;
    };    

    // MyClass.cpp
    class MyClass::MyClassImpl 
    {
        // implementation details go here
    };

    MyClass::MyClass() : impl(new MyClassImpl) {}
    MyClass::~MyClass() { delete impl; }
    void MyClass::doSomething() { /* use impl to do something */ }


In this example, the MyClass class has a private member impl that is a pointer to an implementation class called MyClassImpl. The implementation details of MyClass are hidden from the users of the class, reducing compilation dependencies and improving encapsulation.
It's worth noting that C++11 introduced the std::unique_ptr which can be used to manage the memory of the implementation class and avoid the need to explicitly call the destructor of the implementation class.

C++ Singleton Pattern


The singleton pattern is a design pattern that ensures that a class has only one instance, and provides a global point of access to that instance.
the singleton pattern can be implemented using a combination of a private constructor, a static instance variable, and a static method to access the instance.

    class Singleton

    {

    private:

        static Singleton* instance;

        Singleton() {}


    public:

        static Singleton* getInstance()

        {

            if (instance == nullptr)

            {

                instance = new Singleton;

            }

            return instance;

        }

        void doSomething()

        {

            std::cout << "doing something" << std::endl;

        }

    };

    Singleton* Singleton::instance = nullptr;


In this example, the class Singleton has a private constructor and a private static instance variable. The class also has a public static method getInstance() which creates an instance of the class if it doesn't exist and returns a pointer to the instance.

The getInstance() method uses a simple form of lazy initialization to create the instance of the class only when it is first requested. This is known as the Meyers' Singleton.

To use this singleton, we can call the getInstance() method like this:

    Singleton* singleton = Singleton::getInstance();
    singleton->doSomething();

This will output "doing something" in the console.

It is important to note that in this example, the singleton instance is not thread-safe, if you want to use the singleton in a multi-threaded environment, you need to make sure that the singleton's methods are thread-safe and also need to use a technique such as double-checked locking to ensure that multiple threads do not create multiple instances of the singleton.

It's important to note that the singleton pattern is considered an anti-pattern by some developers as it may cause issues with testing, maintainability, and flexibility, and it's better to use dependency injection instead.

This example is a basic example of the singleton pattern in C++, but in a real-world scenario, it's recommended to use a thread-safe implementation and also to handle the lifetime of the singleton properly.


The singleton pattern is a design pattern that ensures that a class has only one instance and provides a global point of access to it. In C++, a thread-safe version of the singleton pattern can be implemented using a combination of a static instance variable, a private constructor, and a double-checked locking mechanism. Here's an example of a thread-safe singleton class in C++:

    class Singleton 
    {
    private:
        static std::mutex mtx;
        static Singleton* instance;

        // Private constructor to prevent instantiation
        Singleton() {}

    public:
        static Singleton* getInstance()
         {
            if (instance == nullptr) 
            {
            std::unique_lock<std::mutex> lock(mtx);
                if (instance == nullptr) 
                {
                    instance = new Singleton();
                }
            }
            return instance;
        }
    };

    std::mutex Singleton::mtx;
    Singleton* Singleton::instance = nullptr;

In this example, the getInstance() method uses a double-checked locking mechanism to ensure that only one instance of the Singleton class is created, even in a multithreaded environment. The use of the std::mutex and std::unique_lock classes ensure that the instance variable is accessed in a thread-safe manner.

Importance of input impedance of an op amp

Input impedance

The input impedance of an operational amplifier (op-amp) is a crucial parameter that determines how much current the op-amp will draw from the signal source and how much the source's output voltage will be affected by the op amp's input circuitry.

The input impedance of an op-amp is the measure of how much it resists current flow into its input terminals. The higher the input impedance, the less current the op-amp will draw from the source, and the more accurately the op-amp will measure the voltage of the source. On the other hand, a low input impedance will cause the op-amp to draw significant current from the source, which can cause voltage drops across the source's internal impedance, leading to inaccuracies in the signal measurement.

In applications where the signal source is weak, such as in sensors, microphones, or antennas, a high input impedance op-amp is essential to ensure minimal loading of the source and accurate signal measurement. Moreover, in applications where the op-amp is used as a buffer, a high input impedance helps prevent any loading of the input signal.

Overall, the input impedance of an op-amp is an essential parameter that needs to be considered when designing a circuit to ensure accurate signal measurement and minimal loading of the signal source.

Output impedance

The output impedance of an operational amplifier (op-amp) is an important parameter that determines the ability of the op-amp to drive loads with accuracy and stability.

The output impedance of an op-amp is the measure of how much it resists current flow from its output terminal. A low output impedance op amp can supply high current to the load without causing significant voltage drops, thus ensuring accurate voltage and current regulation. On the other hand, a high output impedance op amp will cause voltage drops across its internal impedance, leading to inaccuracies in the load voltage and current measurements.

In applications where the op-amp is used to drive loads, such as in power amplifiers, motor drivers, or LED drivers, a low output impedance is essential to ensure accurate load regulation and stable operation. A low output impedance also ensures that the op-amp can handle capacitive loads without oscillating or ringing.

Moreover, the output impedance of the op-amp is also critical in applications where multiple op-amps are cascaded, such as in filters or amplifiers. In such cases, a low output impedance of the preceding op-amp ensures that the following op-amp is not affected by the loading effects of the preceding stage.

Overall, the output impedance of an op-amp is an essential parameter that needs to be considered when designing a circuit to ensure accurate load regulation, and stable operation and prevent any loading effects on the following stages of the circuit.



Clean Code Tips


Tips for writing clean code

  1. Keep it simple: Avoid using complex and convoluted code. Instead, strive for simplicity and clarity in your code.

  2. Follow a consistent coding style: Use a consistent indentation, naming, and commenting style throughout your code. This makes it easier to read and understand.

  3. Use meaningful names: Use descriptive and meaningful names for variables, functions, and classes. This makes it clear what the code is doing.

  4. Write self-documenting code: Use clear and expressive code that explains itself, rather than relying on comments to explain what the code is doing.

  5. Keep functions small: Break up large functions into smaller, more manageable chunks. A good rule of thumb is to keep functions to less than 20 lines of code.

  6. Avoid duplication: Don't repeat the same code in multiple places. Instead, extract common functionality into reusable functions or classes.

  7. Use white space effectively: Use blank lines, indentation, and white space to visually separate different sections of code, making it easier to read and understand.

  8. Use design patterns: Use well-established design patterns to solve common problems in a consistent and maintainable way.

  9. Write tests: Write automated tests to ensure that your code is working as expected and to make it easier to refactor and maintain.

  10. Review and refactor: Regularly review your code and refactor it as needed to keep it clean and maintainable.

It's important to note that writing clean code is a continuous process, and it requires discipline, practice, and continuous improvement, it's also important to keep in mind that clean code is code that is easy to read, understand, and change.

Unicasting, Multicasting and Broadcasting Data Transmission In Computer Networks


 Unicasting, Multicasting, and Broadcasting


Unicasting and multicasting are two different methods of data transmission in computer networks.

Unicasting is the process of sending a single copy of a data packet to a specific host or group of hosts on a network. This is the most common method of data transmission, and it is used by most application protocols, such as HTTP, FTP, and SSH. When a device wants to send data to another device on a network, it uses the destination device's unique IP address to unicast the packet to that device.

Multicasting, on the other hand, is the process of sending a single copy of a data packet to a specific group of hosts on a network. In multicast, the sender sends a single copy of the data to a multicast IP address, which is a special type of IP address used for multicast transmissions. The multicast IP address is used to identify the group of hosts that should receive the data. Multicasting is less common than unicasting and is typically used for IPTV, IP multicast audio and video conferencing, live streaming, and software updates.

Both unicasting and multicasting have their advantages and disadvantages.

Unicasting is more efficient in terms of network resources because it sends a single copy of the data to the destination device and does not use any additional bandwidth.

Multicasting, on the other hand, is more efficient in terms of sender resources, as it only needs to send one copy of the data, which is then received by all the members of the multicast group.

In conclusion, unicasting is the process of sending a data packet to a specific host or group of hosts, and multicasting is the process of sending a data packet to a specific group of hosts. Unicasting is more common and efficient in terms of network resources, but multicasting is more efficient in terms of sender resources and is typically used for IPTV, IP multicast audio and video conferencing, live streaming, and software updates.

Broadcasting is a third method of transmitting data over a network. It involves sending data from one sender to all devices on the network, without any specific target address. Broadcasting is commonly used for network management and for sending network-wide messages, such as announcing a network outage or a software update. However, broadcasting can be inefficient as it sends data to all devices, regardless of whether they need it or not.

LeetCode C++ Cheat Sheet June

🎯 Core Patterns & Representative Questions 1. Arrays & Hashing Two Sum – hash map → O(n) Contains Duplicate , Product of A...