Hướng dẫn dùng python notimplementederror python
Bài viết này bàn về hướng lập trình giao diện (programming to the interface) thay vì giao diện người dùng, hay giao diện đồ họa (user interface, graphical interface). Python không có sự phân biệt giữa ba khái niệm này. Tuy nhiên, trong thực tế, chúng ta hay viết mã tương tự như sau để mô phỏng một giao diện. class Interface(object): def method_1(self): raise NotImplementedError() def method_2(self): raise NotImplementedError() Các phương thức trong lớp Interface chỉ có một câu lệnh duy nhất là raise NotImplementedError() để cố tình gây ra biệt lệ khi chúng được gọi. Một lớp trừu tượng có thể được mô phỏng như sau: class AbstractClass(Interface): def method_1(self): # làm một việc gì đó pass Lớp AbstractClass là lớp con của Interface và hiện thực hóa phương thức method_1 nhưng vẫn để phương thức method_2 lơ lửng. Cuối cùng, một lớp cụ thể là một lớp con của Interface hoặc AbstractClass nhưng hiện thực hóa tất cả các phương thức. class ConcreteClass(AbstractClass): def method_2(self): # làm một việc gì đó pass Điểm đáng lưu ý là tất cả các lớp này đều hợp lệ trong Python. Chúng ta hoàn toàn có thể khởi tạo một đối tượng của một trong ba lớp này và chỉ đến khi gọi một hàm nào đó thì biệt lệ mới xảy ra. Nói cách khác, biệt lệ NotImplementedError chỉ được phát hiện khi chạy, trong khi đáng lẽ nó đã phải được phát hiện ngay khi dịch. Điều này làm mất giá trị của một hợp đồng, và hạn chế việc áp dụng hướng lập trình giao diện trong Python. Để khắc phục điểm yếu này, tôi đã viết một bộ trang hoàng (decorator) nhỏ để bắt lỗi ngay khi một lớp cụ thể được định nghĩa thiếu. Định nghĩa thiếu ở đây mang ý nghĩa rằng lớp cụ thể đã quên định nghĩa một phương thức nào đó. '''Set of (class) decorators to help with interface programming. Examples:: @concrete class subclass(parent): ... Released to the public domain. Nam T. Nguyen, 2012 ''' import dis import types import unittest def concrete(orig_class): '''A decorator to ensure that a concrete class has no un-implemented methods. An un-implemented method is defined similarly to:: def method(...): raise NotImplementedError() This, when translated to bytecode, looks like:: LOAD_GLOBAL 0 (NotImplementedError) CALL_FUNCTION 1 RAISE_VARARGS 1 ... Or when ``raise NotImplementedError``:: LOAD_GLOBAL 0 (NotImplementedError) RAISE_VARARGS 1 ... The check here is for such pattern. ''' for name in dir(orig_class): func = getattr(orig_class, name) # correct type? if type(func) not in (types.FunctionType, types.MethodType): continue # check if first name is NotImplementedError if len(func.func_code.co_names) < 1 or \ func.func_code.co_names[0] != 'NotImplementedError': continue # and RAISE_VARARGS somewhere after that for position in (3, 6): if len(func.func_code.co_code) < position: continue opcode = ord(func.func_code.co_code[position]) if dis.opname[opcode] == 'RAISE_VARARGS': raise SyntaxError('Function %s.%s must be implemented.' % ( orig_class.__name__, func.func_name)) return orig_class class ConcreteTest(unittest.TestCase): def test_raise(self): class test1: def method(self): raise NotImplementedError self.assertRaises(SyntaxError, concrete, test1) class test2: def method(self): raise NotImplementedError() self.assertRaises(SyntaxError, concrete, test2) def test_not_raise(self): class test: def method(self): return NotImplementedError() concrete(test) def test_subclass(self): class parent(object): def override_me(self): raise NotImplementedError() class test(parent): def leave_it(self): pass self.assertRaises(SyntaxError, concrete, test) if __name__ == '__main__': unittest.main() Để sử dụng bộ trang hoàng concrete này, ta sẽ sửa lại lớp ConcreteClass như sau: @concrete class ConcreteClass(AbstractClass): # như trên Nếu ConcreteClass quên hiện thực hóa một phương thức nào đó, Python sẽ thông báo SyntaxError ngay sau khi ConcreteClass được định nghĩa. Hôm nay mình sẽ đi vào tổng quan khái niệm, sau đó lấy ví dụ bằng Python cho các con vợ dễ hiểu nhé =)) Bài viết gốc trên blog cá nhân của mình: https://phamduyhieu.com/solid-trong-oop-va-vi-du-de-hieu-bang-python 1. Single Responsibility
Ví dụ ta có class Animal, trong đó gồm cả method lưu object vào database - save_to_db. Với thiết kế này, khi chương trình thay đổi database, ta phải mò vào sửa class gốc này. Thay vào đó ta tạo thêm 1 class nhỏ dành riêng cho việc lưu trữ database là AnimalDB. Việc này giúp cho việc sửa chữa đơn giản, rõ ràng hơn, ít bug hơn. 2. Open Close Principle
Thiết kế trên đã vi phạm nguyên lý OCP, giả dụ mỗi tuần có thêm 1 case khách hàng mới, chúng ta lại phải sửa class Discount, logic hàm give_discount sẽ dài ra vô tận Thay vào đó, ta nên tạo 1 class mới kế thừa class cũ 3. Liskov Substitution Principle
VD: viết chương trình mô tả các loài chim bay Có class chimcanhcut cũng là chim nên cho kế thừa class Bird => Khi gọi hàm bay của object chim cánh cụt sẽ bị Exception => thiết kế này vi phạm nguyên lý LSP Nôm na khi thiết kế class phải chú ý, tránh bê nguyên các mối quan hệ của các object ngoài đời sống vào code. A là B không có nghĩa là A nên kế thừa B (nếu class A không thể thay thế được class B)
Với thiết kế bên trên, object lion có thể thay thế object animal từ class Animal mà chương trình vẫn chạy đúng. 4. Interface segregation Principle• Nên tách interface thành các interface nhỏ hơn phục vụ cho những mục đích cụ thể
Với Interface MyInterface, các class khi implement MyInterface sẽ phải implement tất cả các method trong nó. Điều này thành ra bất hợp lý, đôi khi gây dư thừa vì 1 class đôi khi không dùng hết tất cả các method. Vì vậy ta nên chia thành các interface nhỏ (DBInterface, DisplayInterface) gồm các method liên quan đến nhau, dễ quản lý, dễ implement hơn. 5. Dependency Inversion Principle
Ở đây ta có các module cấp thấp là Bread và Pizza, module cấp cao là Production. 2 module này giao tiếp với nhau bằng interface IFood, giúp cho chương trình trở lên linh hoạt hơn. Module Production chỉ cần sử dụng các method trong IFood mà không bị ràng buộc hay cần quan tâm object nào sẽ được truyền vào. Ta có thể truyền vào pizza hoặc bread. |