Thư viện sách cá nhân

Oct 09 2020

Rất mới đối với OOP với Python - và cảm thấy như tôi vẫn chưa hoàn toàn hiểu được OOP. Tuy nhiên, tôi nghĩ rằng tôi sẽ tập hợp một thư viện sách trực tuyến cá nhân để thực hành kế thừa, phương pháp, docstrings, v.v.

Chương trình cho phép một người thêm sách (truyện tranh hoặc tiểu thuyết - với truyện tranh kế thừa từ tiểu thuyết) vào thư viện của riêng mình và tạo danh sách đọc tùy chỉnh.

Có một số phương pháp khác nhau. Ví dụ: một phương thức thư viện sẽ trả về tất cả các sách trên một độ dài được chỉ định.

Lưu ý rằng tôi đã loại bỏ khá nhiều từ này, vì vậy nó không dài 5 trang! Tôi thường có một số loại sách khác nhau, các thuộc tính chẳng hạn như Tác giả, v.v. Nhưng hy vọng từ phần bên dưới bạn sẽ hiểu rõ về cấu trúc.

Nói về cấu trúc, tôi chỉ muốn biết liệu của tôi có tốt không. Ngoài ra, có một vấn đề là nếu bạn xóa một cuốn sách khỏi thư viện, cuốn sách đó sẽ vẫn hiển thị trong bất kỳ danh sách đọc nào tôi đã tạo. Tôi không biết có cách nào để giải quyết vấn đề này không, hay tôi đang nghĩ về điều này sai cách.

class Library:
    
    def __init__(self, books = None):
        if books is None:
            self.books = []
        else:
            self.books = books

    def add_book(self, book):
        self.books.append(book)
        
    def remove_book(self, book):
        self.books.remove(book)
        
    def pages_over_n(self, number):
        """Returns books that have more than specified number of pages"""
        return [book for book in self.books if book.pages > number]
    
    def get_library(self):
        """Returns all books in Library"""
        return self.books
    
    def __repr__(self):
        return f"Library({self.books})"
    
    def __str__(self):
        return f"Library has {len(self.books)}"

class Novel:
    
    def __init__(self, title, pages, publicationDate):
        self.title = title
        self.pages = pages
        self.publicationDate = publicationDate
        
    def __repr__(self):
        return f"Novel({self.title}, {self.pages}, {self.publicationDate})"
    
    def __str__(self):
        return f"Media: {self.title}"
    
class ComicBook(Novel):
    
    def __init__(self, title, pages, publicationDate, artist):
        super().__init__(title, pages, publicationDate)
        self.artist = artist
        
    def get_artist(self):
        return f"Artist: {self.artist}"
        
    def __repr__(self):
        return (f"ComicBook({self.title}, {self.pages}, " 
                f"{self.publicationDate}, {self.artist})")
    
class ReadingList:
    
    def __init__(self):
        self.list = []
    
    def add_book(self, book):
        """Add book to reading list"""
        self.list.append(book)
        
    def remove_book(self, book):
        """Remove book from reading list"""
        self.list.remove(book)

    def total_pages(self):
        """Returns total pages in reading list"""
        total = 0
        for book in self.list:
            total += book.pages
        return total

    def __repr__(self):
        return f"ReadingList({self.list})"
    
    def __str__(self):
        return f"Reading list of size {len(self.list)}"

# Initialise Library
library = Library()

# Create a few books
novel1 = Novel("Harry Potter", 500, 1991)
novel2 = Novel("LotR", 1000, 1960)
novel3 = Novel("Game of Thrones", 2000, 2018)
comic1 = ComicBook("Batman", 100, 2020, "Stan Lee")

# Add books to library.
library.add_book(novel1)
library.add_book(novel2)
library.add_book(novel3)
library.add_book(comic1)

# Create a new reading list.
readingListOne = ReadingList()

# Add a couple of books to reading list.
readingListOne.add_book(novel1)
readingListOne.add_book(comic1)

Trả lời

8 AryanParekh Oct 09 2020 at 18:01

Quan sát chung

Tôi đang trung thực 100% ở đây, Khi tôi chỉ nhìn lướt qua mã của bạn, không có gì đáng kinh ngạc ngay lập tức với nó.

  • Bạn không nên sử dụng tên biến / hàm đã được xác định trong Python. Lớp của bạn ReadingListcó một thuộc tính list. Đây là một ý tưởng tồi vì đã có một listtừ khóa có thể mâu thuẫn với định nghĩa của bạn

Cấu trúc mã

Bạn đã cố gắng thực hiện ý tưởng kế thừa, mặc dù nó ổn, sẽ tốt hơn nhiều nếu có lớp cơ sở của bạn Book.

Điều này có ý nghĩa hơn vì thực tế là tiểu thuyết và truyện tranh là các loại sách . Và do đó có thể chia sẻ các thuộc tính và phương thức.

BookLớp của bạn có thể có các thuộc tính và phương thức như

  • Tác giả
  • Tên
  • Nội dung phổ biến trong tất cả các loại sách

Và lớp con của bạn Novelcó thể có

  • Sutff đặc biệt trong Tiểu thuyết

Một cách triển khai rất cơ bản

class Book():
    def __init__(self,author,name):
        self.author = author
        self.name = name

    def print_book(self):
        print(f"{self.name} by {self.author}")




class Novel(Book):
    def __init__(self,author,name):
        Book.__init__(self,author,name)

    def display(self):
        print(f"The Novel {self.name}")
        # Novel stuff which isn't common in all books

class Comic(Book):
    def __init__(self,author,name):
        Book.__init__(self,author,name)

    def display(self):
        print(f"The comic book {self.name}")
        # Comic book stuff which isn't common in all books
        

nov = Novel("J.K Rowling","Harry Potter")
nov.print_book()

Vấn đề là Novellớp học của bạn về cơ bản Booklà một tác giả, các trang, v.v. Nhưng nó cũng có một số thứ mà những cuốn sách khác Comickhông thích. Bằng cách này, bạn không cần phải tạo một lớp hoàn toàn mới, nhưng bạn có thể chỉ cần kế thừa cái chung từ Booklớp đó và thêm những thứ bổ sung vàoNovel


Tạo thư viện

Bạn có thể dừng lại ở đây nếu muốn, và coi Booklớp học của bạn như một thư viện. Nhưng nếu bạn muốn trở nên điên rồ hơn, bạn có thể có một lớp khác được gọi Magazinevà đó sẽ là lớp con của cha Library. Đây là những gì sẽ trông như thế nào.

Nhưng điều này sẽ được khá phức tạp, hơn nữa, nó sẽ không làm cho rất nhiều ý nghĩa để kế thừa Booktừ Library. Do đó, tốt hơn là bạn nên giữ Libraryriêng biệt để kiểm soát các cuốn sách.



class Library():
    def __init__(self,book_list = []):
        self.book_list = book_list

    def new_book(self,book):
        self.book_list.append(book)

    def remove_book(self,name): # book is the name of the book
        for book in self.book_list:
            if book.name == name:
                self.book_list.remove(book)
                break


    def print_books(self):
        for book in self.book_list:
            print(f"Book {book.name} by {book.author}")


class Book():
    def __init__(self,author,name):
        self.author = author
        self.name = name

    def print_book(self):
        print(f"{self.name} by {self.author}")




class Novel(Book):
    def __init__(self,author,name):
        Book.__init__(self,author,name)

    def display(self):
        print(f"The Novel {self.name}")
        # Novel stuff which isn't common in all books

class Comic(Book):
    def __init__(self,author,name):
        Book.__init__(self,author,name)

    def display(self):
        print(f"The comic book {self.name}")
        # Comic book stuff which isn't common in all books


my_lib = Library()
my_lib.new_book(Novel("J.K Rowling","Harry Potter"))
my_lib.new_book(Novel("Aryan Parekh","Stack"))

my_lib.remove_book("Stack")

my_lib.print_books()
9 G.Sliepen Oct 09 2020 at 18:01

Đơn giản hóa hàm tạo của class Library

Bạn có một giá trị mặc định Nonecho tham số books, nhưng sau đó ngay lập tức tiến hành chuyển đổi giá trị đó Nonethành một danh sách trống. Tại sao không sử dụng []làm giá trị mặc định?

class Library:
    def __init__(self, books = []):
        self.books = list(books)

    ...

Lưu ý rằng để tránh sự cố của các đối số mặc định có thể thay đổi như được đề cập bởi ojdo, bạn nên tạo một bản sao của danh sách ban đầu.

Hãy kiên định

Bạn lấy một danh sách tùy chọn các sách trong hàm tạo của class Library, nhưng không có thứ đó trong class ReadingList. Đó là một chút ngạc nhiên.

Cũng nên nhất quán với việc đặt tên cho mọi thứ. Trong class Library, danh sách sách được gọi books, nhưng class ReadlingListbạn đã đặt tên cho nó list. Tôi sẽ đặt tên cho nó bookstrong cả hai trường hợp.

Cũng nhất quán với tên của các hàm:

  • get_library()nên là get_books(), vì nó chỉ trả về danh sách sách, không phải là một đối tượng của kiểu Library.
  • pages_over_n()phải là get_books_with_over_n_pages()hoặc một cái gì đó tương tự, để chỉ ra rằng nó trả về Books, không phải trang.
  • total_pages()nên được đổi tên get_total_pages()hoặc calculate_total_pages(). Ưu tiên sử dụng động từ cho tên hàm và danh từ cho biến.

Và tôi cũng thắc mắc tại sao a ComicBookcó artist, mà a Novelkhông có author? Và tại sao có get_artist()nhưng không get_title(), get_publication_date()và như vậy? Làm cho mọi thứ nhất quán sẽ làm cho việc sử dụng các lớp của bạn dễ dàng hơn nhiều.

Xóa sách khỏi Thư viện

Như bạn đã nhận thấy, bạn có thể xóa một cuốn sách khỏi thư viện, nhưng nó sẽ không bị xóa khỏi bất kỳ danh sách đọc nào. Nhưng điều đó là tốt. Tại sao một cuốn sách sẽ biến mất một cách kỳ diệu khỏi danh sách đọc trong đời thực nếu thư viện của bạn xóa cuốn sách đó? Lưu ý rằng mã của bạn hoàn toàn ổn, Python sẽ không thực sự xóa Bookđối tượng khỏi bộ nhớ cho đến khi tham chiếu cuối cùng đến nó biến mất.

Tuy nhiên, nếu bạn muốn một cuốn sách biến mất kể từ thời ReadingListđiểm nó bị xóa khỏi Library, thì bằng cách nào đó cần phải có một số liên lạc giữa hai đối tượng đó. Có hai lựa chọn:

  1. A Librarylưu giữ một danh sách các ReadingLists tham chiếu đến các Books của nó và khi bạn gọi remove_book()a Library, nó sẽ lần lượt lặp lại tất cả các ReadingLists và cũng gọi remove_book()chúng.

  2. A ReadingListgiữ một tham chiếu đến cái Librarymà Booknó chứa. Bất cứ khi nào bạn gọi một hàm thành viên truy cập danh sách sách trong danh sách đang đọc, trước tiên hàm thành viên này phải lọc ra tất cả những cuốn sách không còn trong danh sách Library.

7 JoseFernandoLopezFernandez Oct 09 2020 at 19:37

Vì Libraryvà ReadingListchỉ là các loại danh sách sách khác nhau, tôi sẽ chọn xác định một loại chung BookListlàm lớp cơ sở mà từ đó Libraryvà ReadingListcả hai đều kế thừa.

Làm điều này giúp bạn không phải lặp lại việc phải xác định các phương thức chung như sau, điều mà cả hai lớp của bạn đã làm. Dưới đây là một số phương thức từ Librarylớp.

def __init__(self, books = None):
    if books is None:
        self.books = []
    else:
        self.books = books

def add_book(self, book):
    self.books.append(book)
    
def remove_book(self, book):
    self.books.remove(book)

Và đây là các phương thức tương tự từ ReadingListlớp.

def __init__(self):
    self.list = []

def add_book(self, book):
    """Add book to reading list"""
    self.list.append(book)
    
def remove_book(self, book):
    """Remove book from reading list"""
    self.list.remove(book)

Như G. Sliepen đã nói, bạn nên đơn giản hóa hàm Librarytạo để khớp với hàm ReadingListtạo. Sau khi bạn làm điều này, các phương pháp hoàn toàn giống nhau.

Bằng cách xác định một lớp danh sách sách chung, bạn không những không phải tiếp tục xác định lại chức năng chung mà còn có thể tận dụng các hàm đa hình.

Ví dụ: thay vì làm cho mỗi lớp chịu trách nhiệm xuất ra một biểu diễn chuỗi của chính nó, bạn có thể xác định một lớp BookListHTMLWriterlấy một mục danh sách sách (hoặc a ReadingListhoặc a Library, cả hai đều tốt khi chúng bắt nguồn từ cùng một BookListđối tượng) và xuất ra một Biểu diễn bảng HTML.

Tương tự như vậy, bạn cũng có thể xác định một BookListXMLWriterkết xuất biểu diễn XML của một BookListđối tượng hoặc một lớp con của nó. Bạn có thể mất OOP-refactoring này hơn nữa bằng cách sau đó sắp xếp các thông thường chức năng cho BookListHTMLWritervà BookListXMLWritercác lớp học và lấy chúng từ một cơ sở BookListWriterlớp cơ sở.

Nói một cách lạc quan, lập trình hướng đối tượng có thể là một chút sở thích (và một chút bí truyền), nhưng một khi bạn bắt đầu nghĩ về những thuật ngữ này, đó là một mô hình tinh thần hữu ích, ngay cả khi nó không phải là mô hình duy nhất của bạn. Hoặc bạn thích một, thậm chí.


Về quan điểm của bạn về cách xóa sách khỏi thư viện không xóa sách đó khỏi (các) danh sách đọc của bạn, tôi đồng ý với các phản hồi trước rằng điều này có ý nghĩa. Tôi nghĩ vấn đề mà bạn đang ám chỉ có thể là câu hỏi về tính toàn vẹn tham chiếu, điều mà tôi cũng nhận thấy.

Bạn rõ ràng đã nhận thấy điều đó Libraryvà ReadingListcả hai đều là danh sách của Books. Khi đọc qua mã, tôi luôn bắt đầu hình dung lược đồ cơ sở dữ liệu tương ứng và vấn đề mà tôi nghĩ bạn đang đề cập đến là phải có một danh sách chính gồm các sách đã được tạo để bạn có thể thêm vào thư viện cá nhân và / hoặc danh sách đọc , có lẽ tùy thuộc vào việc bạn sở hữu cuốn sách hay không. Tôi sẽ gọi danh sách chính này là "danh mục" hoặc một cái gì đó để phân biệt danh sách chính này với danh sách của một loại cụ thể thuộc về một người dùng cụ thể.

Hơn nữa, danh mục này sẽ đơn giản là tất cả các bản ghi trong một booksbảng trong cơ sở dữ liệu của bạn.

Để thực hiện một cơ sở dữ liệu cho chương trình này, bạn có thể bắt đầu bằng cách tạo ra booksvà usersbảng biểu, và sau đó tạo ra một bảng thứ ba bao gồm duy nhất của id của người dùng và id của một cuốn sách. Truy vấn bảng liên kết này cho một id người dùng nhất định sẽ cung cấp cho bạn tất cả sách mà người này sở hữu.

Nếu bạn muốn người dùng có thể tạo thư viện tùy ý không chỉ dựa trên việc họ sở hữu sách hay không, bạn chỉ cần tạo một reading_listsbảng với id người dùng làm trường để chỉ ra chủ sở hữu và sau đó tạo một bảng liên kết khác bằng cách sử dụng danh sách id và một id sách.

Điều này sẽ giải quyết vấn đề toàn vẹn tham chiếu của bạn bằng cách ngăn việc xóa hồ sơ sách trừ khi bạn chỉ định một ON DELETEhành động thích hợp trong bookslược đồ bảng.

Việc phân loại Books thành Novels và Magazines làm phức tạp mọi thứ bởi vì nếu hệ thống phân cấp kế thừa thấm nhuần sự khác biệt đủ phức tạp trên các lớp con của bạn, các đối tượng của bạn không còn ánh xạ độc đáo thành các bảng đơn lẻ (giả sử bạn muốn một lược đồ có thể chuẩn hóa hợp lý).

Nếu bạn đạt được điểm này, một công cụ lập bản đồ quan hệ thực thể tốt có thể giúp bạn tiết kiệm hàng giờ đồng hồ thất vọng.