# ABC, ABCMeta

## ABC

***

Helper class，其 metaclass 為 `ABCMeta`。當一個 Class 繼承自 `ABC` 時，它就成為 Abstract Base Class。

{% code title="PYTHON" %}

```python
abc.ABC
```

{% endcode %}

{% hint style="info" %}
**`ABC` 是包含多個 abstract 方法和 abstract 屬性的 Class，但需要 non-abstract subclass，來提供實現方法和屬性。**&#x20;

這是一種比建立自定義 metaclass 更方便的技術。
{% endhint %}

**Example 1: Basic usage**

{% code title="PYTHON" %}

```python
from abc import ABC, abstractmethod

class Vehicle(ABC):
    @abstractmethod
    def max_speed(self):
        pass

class Car(Vehicle):
    @property
    def max_speed(self):
        return 200

car = Car()
print(f"Car's max speed: {car.max_speed} km/h") # Output: 200 km/h
```

{% endcode %}

**Example 2: Basic usage**

{% code title="PYTHON" %}

```python
from abc import ABC, abstractmethod

class Shape(ABC):
    @abstractmethod
    def area(self):
        pass

    @abstractmethod
    def perimeter(self):
        pass

class Rectangle(Shape):
    def __init__(self, width, height):
        self.width = width
        self.height = height

    def area(self):
        return self.width * self.height

    def perimeter(self):
        return 2 * (self.width + self.height)

# Trying to instantiate Shape will raise an error
# shape = Shape()

# Working with a concrete implementation
rect = Rectangle(10, 20)
print(f"Rectangle area: {rect.area()}")            # Output: 200
print(f"Rectangle perimeter: {rect.perimeter()}")  # Output: 60
```

{% endcode %}

## ABCMeta

***

定義 Abstract Base Classes (ABCs) 的 metaclass。 Python 中的 metaclass 是 class 的 class，它定義了 class 的行為方式。 `abc.ABCMeta` 提供了在 ABC 中定義 abstract 方法和屬性的機制。

如果不想繼承 `ABC`，可以透過在 Class 中設定 `metaclass=ABCMeta` ，來建立 Abstract Base Class。

{% code title="PYTHON" %}

```python
abc.ABCMeta
```

{% endcode %}

{% hint style="info" %}
**雖然您可以直接使用 `abc.ABCMeta`，但從 `abc.ABC` 繼承通常更簡單且更具可讀性，它抽象化了 metaclasses 的直接使用。**
{% endhint %}

**Example 1: Basic usage**

{% code title="PYTHON" %}

```python
from abc import ABCMeta, abstractmethod

class Graphic(metaclass=ABCMeta):
    @abstractmethod
    def draw(self):
        pass

class Circle(Graphic):
    def draw(self):
        print("Drawing a circle")

# Trying to instantiate Graphic will raise an error
# graphic = Graphic()

# Working with a concrete implementation
circle = Circle()
circle.draw()  # Output: Drawing a circle
```

{% endcode %}

**Example 2: Basic usage**

{% code title="PYTHON" %}

```python
from abc import ABCMeta, abstractmethod, abstractproperty

class Vehicle(metaclass=ABCMeta):
    @abstractproperty
    def max_speed(self):
        pass

class Car(Vehicle):
    @property
    def max_speed(self):
        return 150

car = Car()
print(f"Car's max speed: {car.max_speed} km/h")
```

{% endcode %}

### register()

***

用於將具體 class 註冊為 abstract base class 的 "Virtual subclass"，這意味著即使它沒有繼承自 ABC，但仍能被 `issubclass()` 和 `isinstance()` 等函數識別為 subclass。

{% code title="PYTHON" %}

```python
@ABC.register
```

{% endcode %}

{% code title="PYTHON" %}

```python
ABC.register(Subclass)
```

{% endcode %}

{% hint style="info" %}
**Virtual subclass: 視為另一個 Class 的 subclass，但實際上並沒有繼承自另一個 Class。**
{% endhint %}

{% hint style="info" %}
**`issubclass()` 和 `isinstance()` 會將註冊的 classes，視為 subclasses，即使它們不是從傳統意義上的 ABC 繼承。**
{% endhint %}

{% hint style="info" %}
**盡可能使用 `register()` 作為 decorator，因為它使 ABC 和註冊 class 之間的關係更加明顯。**&#x20;

請務必記錄 `register()` 的使用，因為一個 class 被視為 ABC 的 subclass 的原因，並不總是顯而易見的。
{% endhint %}

**Example 1: Using Decorator**

{% code title="PYTHON" %}

```python
from abc import ABC, abstractmethod

class MyABC(ABC):
    @abstractmethod
    def do_something(self):
        pass

@MyABC.register
class ConcreteClass:
    @property
    def do_something(self):
        return "Doing something in ConcreteClass"

print(issubclass(ConcreteClass, MyABC))  # Output: True
print(issubclass(ConcreteClass, ABC))    # Output: True

x = ConcreteClass()
print(x.do_something)  # Output: Doing something in ConcreteClass
```

{% endcode %}

**Example 2: Using Method Call**

{% code title="PYTHON" %}

```python
from abc import ABC, abstractmethod

class MyABC(ABC):
    @abstractmethod
    def do_something(self):
        pass

class AnotherClass:
    @property
    def do_something_else(self):
        return "Doing something in ConcreteClass"

# Registering using method call syntax
MyABC.register(AnotherClass)

# AnotherClass is now considered a subclass of MyABC
print(issubclass(AnotherClass, MyABC))  # Output: True
print(issubclass(AnotherClass, ABC))    # Output: True

x = AnotherClass()
print(x.do_something_else)  # Output: Doing something in ConcreteClass
```

{% endcode %}

**Example 3: Basic usage**&#x20;

當一個 class 繼承自 parent class 時，parent class 會以其方法解析順序 (MRO) 出現，這是 Python 在呼叫方法時，查找方法的順序。

但是，如果一個 class 被註冊為 ABC 的虛擬 subclass，則該 ABC 不會出現在其 MRO 中。這表示 ABC 中定義的方法，不會自動可供虛擬 subclass 使用，而且您不能在虛擬 subclass 中使用 `super()` 來呼叫這些方法。

{% code title="PYTHON" %}

```python
from abc import ABC, ABCMeta, abstractmethod

# Defining the ABC
class MyAbstractBaseClass(metaclass=ABCMeta):
    @abstractmethod
    def my_method(self):
        pass

# Registering a virtual subclass
class UnrelatedClass:
    def my_method(self):
        print("Implementation in UnrelatedClass")

MyAbstractBaseClass.register(UnrelatedClass)

print(issubclass(UnrelatedClass, MyAbstractBaseClass))  # Outputs: True
print(issubclass(MyAbstractBaseClass, ABC))             # Outputs: False
print(issubclass(MyAbstractBaseClass, ABCMeta))         # Outputs: False

# =========================================================
# Defining the ABC
class MyAbstractBaseClass(ABC):
    @abstractmethod
    def my_method(self):
        pass

# Registering a virtual subclass
class UnrelatedClass:
    def my_method(self):
        print("Implementation in UnrelatedClass")

MyAbstractBaseClass.register(UnrelatedClass)

print(issubclass(UnrelatedClass, MyAbstractBaseClass))  # Outputs: True
print(issubclass(MyAbstractBaseClass, ABC))             # Outputs: True
print(issubclass(MyAbstractBaseClass, ABCMeta))         # Outputs: False
```

{% endcode %}

**Example 4: 影像處理**

{% code title="PYTHON" %}

```python
import cv2
import numpy as np
from abc import ABC, abstractmethod

class ImageProcessor(ABC):
    @abstractmethod
    def process(self, image: np.ndarray) -> np.ndarray:
        """Process an image and return the result."""
        pass


class GrayscaleConverter:
    def process(self, image: np.ndarray) -> np.ndarray:
        return cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)

# Registering with the abstract base class
ImageProcessor.register(GrayscaleConverter)


class ImageBlurrer:
    def process(self, image: np.ndarray) -> np.ndarray:
        return cv2.GaussianBlur(image, (5, 5), 0)

# Registering with the abstract base class
ImageProcessor.register(ImageBlurrer)


# Checking if the classes are registered as subclasses
print(issubclass(GrayscaleConverter, ImageProcessor))
print(issubclass(ImageBlurrer, ImageProcessor))

# Example usage
image = cv2.imread('113833397_p0.jpg')
grayscale_converter = GrayscaleConverter()
blurrer = ImageBlurrer()

gray_image = grayscale_converter.process(image)
blurred_image = blurrer.process(image)
```

{% endcode %}

### @abstractmethod

***

當此 decorator 應用於 abstract base class 中的方法時，表示該方法是抽象的。這意味著必須在 ABC 的任何非抽象 subclass 中重寫該方法。

{% code title="PYTHON" %}

```python
@abc.abstractmethod
```

{% endcode %}

{% hint style="success" %}
**`@abc.abstractmethod` 只能影響使用繼承衍生的 subclass； 使用 `register()` 註冊的 Virtual subclass 不受影響。**
{% endhint %}

{% hint style="info" %}
**使用 abstract base classes 和 `@abc.abstractmethod` 主要為一組 subclasses 定義公共 interface。**
{% endhint %}

{% hint style="info" %}
**清楚記錄每個抽象方法應該做什麼，因為這可以作為開發人員實作 subclasses 的指南。**
{% endhint %}

{% hint style="info" %}
**當 `@abc.abstractmethod` 與其他 decorator 結合使用時，應將 `abstractmethod` 作為最內層 decorator。**

例如: `@property`、`@classmethod` 和 `@staticmethod`。
{% endhint %}

**Example 1: Basic usage**

{% code title="PYTHON" %}

```python
import abc
import math

class Shape(abc.ABC):
    @abc.abstractmethod
    def area(self):
        """Method to calculate the area of the shape."""
        pass


class Circle(Shape):
    def __init__(self, radius):
        self.radius = radius

    def area(self):
        return math.pi * self.radius ** 2


class Rectangle(Shape):
    def __init__(self, length, width):
        self.length = length
        self.width = width

    def area(self):
        return self.length * self.width


def print_area(shape):
    print(f"The area of the {shape.__class__.__name__} is {shape.area()}")

# Create instances and use them
circle = Circle(5)
rectangle = Rectangle(4, 6)

print_area(circle)      # Output: The area of the Circle is 78.539
print_area(rectangle)   # Output: The area of the Rectangle is 24
```

{% endcode %}

**Example 2: Basic usage**

{% code title="PYTHON" %}

```python
import abc

class ElectronicDevice(abc.ABC):
    @abc.abstractmethod
    def turn_on(self):
        pass

    @abc.abstractmethod
    def turn_off(self):
        pass

    @property
    @abc.abstractmethod
    def is_on(self):
        pass

class Television(ElectronicDevice):
    def __init__(self):
        self._is_on = False

    def turn_on(self):
        if not self._is_on:
            self._is_on = True
            print("Television is now ON.")

    def turn_off(self):
        if self._is_on:
            self._is_on = False
            print("Television is now OFF.")

    @property
    def is_on(self):
        return self._is_on

# Function to demonstrate usage
def test_device(device):
    print(f"Is the device on? {device.is_on}")
    device.turn_on()
    print(f"Is the device on? {device.is_on}")
    device.turn_off()
    print(f"Is the device on? {device.is_on}")

# Create an instance and use it
tv = Television()
test_device(tv)
```

{% endcode %}

**執行結果:**

```txt
Is the device on? No

Television is now ON.
Is the device on? Yes

Television is now OFF.
Is the device on? No
```

**Example 3: 抽象 Properties**

{% code title="PYTHON" %}

```python
import abc

class Vehicle(abc.ABC):
    @property
    @abc.abstractmethod
    def number_of_wheels(self):
        """Abstract property for the number of wheels in a vehicle."""
        pass

class Car(Vehicle):
    @property
    def number_of_wheels(self):
        return 4

class Motorcycle(Vehicle):
    @property
    def number_of_wheels(self):
        return 2

# Testing the implementation
car = Car()
motorcycle = Motorcycle()

print(f"A car has {car.number_of_wheels} wheels.")
print(f"A motorcycle has {motorcycle.number_of_wheels} wheels.")
# Output: A car has 4 wheels..
# Output: A motorcycle has 2 wheels.
```

{% endcode %}

**Example 4: 混合具體方法和抽象方法**

{% code title="PYTHON" %}

```python
import abc

class Computer(abc.ABC):
    @abc.abstractmethod
    def processor(self):
        """Abstract method to define the type of processor."""
        pass

    def boot(self):
        """Concrete method to boot the computer."""
        print(f"Booting up with a {self.processor()} processor.")

class Laptop(Computer):
    def processor(self):
        return "Intel Core i7"

class Server(Computer):
    def processor(self):
        return "AMD Ryzen 9"

# Testing the implementation
laptop = Laptop()
server = Server()

laptop.boot()  # Output: Booting up with a Intel Core i7 processor.
server.boot()  # Output: Booting up with a AMD Ryzen 9 processor.

```

{% endcode %}

**Example 5: 影像處理**

{% code title="PYTHON" %}

```python
import abc
import cv2
import numpy as np

class ImageProcessor(abc.ABC):
    @abc.abstractmethod
    def process(self, image):
        """
        Process an image and return the result.
        """
        pass

    def load_image(self, path):
        """
        Load an image from a given path.
        """
        return cv2.imread(path, cv2.IMREAD_COLOR)

    def save_image(self, image, path):
        """
        Save an image to a given path.
        """
        cv2.imwrite(path, image)


class GaussianBlurProcessor(ImageProcessor):
    def process(self, image):
        """
        Apply Gaussian blur to the image.
        """
        return cv2.GaussianBlur(image, (5, 5), 0)
    

class CannyEdgeProcessor(ImageProcessor):
    def process(self, image):
        """
        Apply Canny Edge Detection to the image.
        """
        gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
        edges = cv2.Canny(gray, 100, 200)
        return edges


def main():
    input_path = '113833397_p0.jpg'
    output_path_blur = 'output_blur.jpg'
    output_path_edges = 'output_edges.jpg'

    # Initialize processors
    blur_processor = GaussianBlurProcessor()
    edge_processor = CannyEdgeProcessor()

    # Load image
    image = blur_processor.load_image(input_path)

    # Process and save images
    blurred_image = blur_processor.process(image)
    blur_processor.save_image(blurred_image, output_path_blur)

    edge_image = edge_processor.process(image)
    edge_processor.save_image(edge_image, output_path_edges)


if __name__ == "__main__":
    main()
```

{% endcode %}
