pythondesign-patternsabstract-factory

How to use callables instead of Abstract Factory Pattern in Python?


I have been trying to figure out when and where to use different patterns in Python. I came across this document: https://python-patterns.guide/gang-of-four/abstract-factory/

Now this made me wonder how can I do what he says in my code. Here is my implementation is an abstract factory. Not sure if its correct.

from abc import abstractmethod, ABC

from sqlalchemy.orm import Session

from managers.database_manager import DatabaseManager
from managers.log_manager import LogManager
from managers.sqlalchemy_manager import get_db
from models.bal.post import Post
from models.dal.models import Post as ORMPost


class PostsManager(ABC):
    def __init__(self):
        pass

    @abstractmethod
    def get_posts(self):
        pass

    @abstractmethod
    def get_post(self, post_id):
        pass

    @abstractmethod
    def create_post(self, post: Post):
        pass

    @abstractmethod
    def delete_post(self, post_id):
        pass

    @abstractmethod
    def update_post(self, post_id, post: Post):
        pass


class PostsManagerFactory:
    @staticmethod
    def get_posts_manager(use_orm=True) -> PostsManager:
        if use_orm:
            return PostsManagerWithORM()
        else:
            return PostsManagerWithoutORM()


class PostsManagerWithORM(PostsManager):

    def get_posts(self):
        db: Session = get_db()
        posts = db.query(ORMPost).all()
        return posts

    def get_post(self, post_id):
        pass

    def create_post(self, post: Post):
        pass

    def delete_post(self, post_id):
        pass

    def update_post(self, post_id, post: Post):
        pass


class PostsManagerWithoutORM(PostsManager):
    def __init__(self):
        super().__init__()
        self.db_manager = DatabaseManager()

    def get_posts(self):
        posts = self.db_manager.execute_query("select * from posts")
        return posts

    def get_post(self, post_id):
        post = self.db_manager.execute_query("SELECT * FROM posts WHERE id='%s'", (post_id,), single_record_flag=True)
        return post

    def create_post(self, post: Post):
        post = self.db_manager.execute_query("INSERT INTO posts (title, content) VALUES (%s, %s) RETURNING *",
                                             (post.title, post.content), single_record_flag=True)

        return post

    def delete_post(self, post_id):
        post = self.db_manager.execute_query("DELETE FROM posts WHERE id = %s RETURNING *", (post_id,),
                                             single_record_flag=True)
        return post

    def update_post(self, post_id, post: Post):
        post = self.db_manager.execute_query(
            "UPDATE posts SET title = %s, content = %s, published = %s WHERE id= %s RETURNING *",
            (post.title, post.content, post.published, post_id),
            single_record_flag=True)
        return post

Here is how I am calling these methods:

posts_manager = PostsManagerFactory.get_posts_manager()
posts = posts_manager.get_posts()

My first question, is it the right way to use abstract factory pattern? If, not please let me know, I will probably ask a new question. Anyway if it is, why does that document say about using callables is better than using abstract factory pattern, how do I do that in this case?


Solution

  • Following you last comment.

    I would not use abstract factory pattern for this use case. May I ask why you want to use it ?

    When I suggest to remove the PostsManagerFactory class by extracting the get_posts_manager to a function, I mean replacing this code snippet

    class PostsManagerFactory:
        @staticmethod
        def get_posts_manager(use_orm=True) -> PostsManager:
            if use_orm:
                return PostsManagerWithORM()
            else:
                return PostsManagerWithoutORM()
    

    with this

    def get_posts_manager(use_orm=True) -> PostsManager:
        if use_orm:
            return PostsManagerWithORM()
        else:
            return PostsManagerWithoutORM()
    

    that you may want to shorten this way

    def get_posts_manager(use_orm=True) -> PostsManager:
        return PostsManagerWithORM() if use_orm else PostsManagerWithoutORM()
    

    Then, you could use it in your code simply by calling the function

    posts_manager = get_posts_manager() # <----
    posts = posts_manager.get_posts()