Các tiên đề của OOP. Các vấn đề về lập trình có cấu trúc Tạo kiểu dữ liệu mới

Ý tưởng chung về lập trình hướng đối tượng và các tính năng của nó đã được thảo luận trong bài học đầu tiên. Ở đây chúng tôi tóm tắt các tài liệu được nghiên cứu trong khóa học này.

Trong Python, tất cả các đối tượng đều được dẫn xuất từ ​​các lớp và kế thừa các thuộc tính từ chúng. Trong trường hợp này, mỗi đối tượng tạo thành không gian tên riêng. Python hỗ trợ những điều này tính năng chính lập trình hướng đối tượng như kế thừa, đóng gói và đa hình. Tuy nhiên, đóng gói theo nghĩa che giấu Dữ liệu Python chỉ hỗ trợ trong quy ước chứ không hỗ trợ cú pháp của ngôn ngữ.

Khóa học không chú ý đến đa kế thừa, khi một lớp con kế thừa từ nhiều lớp cha. Tính kế thừa này được hỗ trợ đầy đủ trong Python và cho phép kết hợp các thuộc tính của hai hoặc nhiều lớp trong một lớp dẫn xuất. Với đa kế thừa, một số tính năng nhất định của tìm kiếm thuộc tính phải được tính đến.

Tính đa hình cho phép các đối tượng các lớp khác nhau có giao diện tương tự. Nó được thực hiện bằng cách khai báo các phương thức trong đó với cùng tên. Các phương pháp nạp chồng toán tử cũng có thể được coi là biểu hiện của tính đa hình như một tính năng của OOP.

Ngoài tính kế thừa, đóng gói và đa hình, còn có các tính năng khác của OOP. Đây là thành phần hoặc tập hợp khi một lớp bao gồm các lệnh gọi đến các lớp khác. Kết quả là, khi tạo một đối tượng từ một lớp tổng hợp, các đối tượng của các lớp khác cũng được tạo ra, đó là các thành phầnĐầu tiên.

Các lớp học thường được đặt trong các mô-đun. Mỗi module có thể chứa nhiều lớp. Đổi lại, các mô-đun có thể được kết hợp thành các gói. Python sử dụng các gói để tổ chức các không gian tên.

Ưu điểm của OOP

Các tính năng của lập trình hướng đối tượng mang lại cho nó một số lợi thế.

Vì vậy OOP cho phép bạn sử dụng cùng một mã chương trình với các dữ liệu khác nhau. Dựa trên các lớp, nhiều đối tượng được tạo ra, mỗi đối tượng có thể có giá trị riêng lĩnh vực. Không cần phải giới thiệu nhiều biến vì các đối tượng có không gian tên riêng. Theo nghĩa này, các đối tượng tương tự như cấu trúc dữ liệu. Một đối tượng có thể được coi như một loại gói dữ liệu mà các công cụ xử lý nó được đính kèm - các phương thức.

Kế thừa cho phép bạn tránh viết Mã mới, nhưng hãy sử dụng và tùy chỉnh một thuộc tính hiện có bằng cách thêm và xác định lại các thuộc tính.

Nhược điểm của OOP

OOP cho phép bạn giảm thời gian viết mã nguồn nhưng lại đảm nhận vai trò lớn hơn phân tích sơ bộ lĩnh vực chủ đề Và thiết kế. Nhiều điều phụ thuộc vào tính đúng đắn của các quyết định ở giai đoạn này hơn là việc viết mã nguồn thực tế.

Cần hiểu rằng cùng một vấn đề có thể được giải quyết bằng nhiều cách khác nhau mô hình đối tượng, mỗi cái sẽ có ưu và nhược điểm riêng. Chỉ một nhà phát triển có kinh nghiệm mới có thể nói cái nào sẽ dễ mở rộng và bảo trì hơn trong tương lai.

Tính năng của OOP trong Python

So với nhiều ngôn ngữ khác, lập trình hướng đối tượng trong Python có một số tính năng đặc biệt.

Mọi thứ đều là một đối tượng - một số, một chuỗi, một danh sách, một hàm, một thể hiện của một lớp, chính lớp đó, một mô-đun. Vì vậy, một lớp là một đối tượng có khả năng tạo ra các đối tượng khác - các thể hiện.

Không có kiểu dữ liệu đơn giản trong Python. Tất cả các loại là các lớp học.

Không có sự nhấn mạnh vào tính đóng gói trong Python. đặc biệt chú ý. Trong các ngôn ngữ lập trình khác, bạn thường không thể truy cập trực tiếp vào thuộc tính được khai báo trong một lớp. Một phương pháp đặc biệt có thể được cung cấp để thay đổi nó. Trong Python, việc truy cập trực tiếp vào các thuộc tính không được coi là đáng trách.

Và cuối cùng

Xét cho cùng, Python là một ngôn ngữ kịch bản được giải thích. Mặc dù họ viết trên đó, trong số những thứ khác: dự án chính, nó thường được sử dụng trong phát triển web, quản trị hệ thốngđể tạo các chương trình tập lệnh nhỏ. Trong trường hợp này, các công cụ ngôn ngữ tích hợp thường là đủ; bạn không cần phải “phát minh” ra các lớp của riêng mình.

Tất cả các ngôn ngữ hướng đối tượng đều sử dụng ba nguyên tắc cơ bản của lập trình hướng đối tượng:

  • đóng gói. Làm sao ngôn ngữ nhất định giấu tính năng bên trong thực hiện đối tượng?
  • Di sản. Ngôn ngữ này cho phép tái sử dụng mã như thế nào?
  • Đa hình. Làm thế nào để một ngôn ngữ nhất định cho phép các đối tượng liên quan được diễn giải một cách thống nhất?

Nguyên tắc đầu tiên của OOP là đóng gói. đóng gói là một cơ chế lập trình liên kết mã và dữ liệu mà nó thao tác với nhau, bảo vệ chúng khỏi truy cập bên ngoài và ứng dụng không chính xác cũng như ẩn chi tiết triển khai bằng ngôn ngữ. Ví dụ: giả sử chúng ta đang sử dụng một lớp đại diện cho các đối tượng như Pen. Một lớp như vậy gói gọn khả năng nội tại của các đối tượng để vẽ các điểm, đường và hình dạng có độ dày và màu sắc khác nhau. Nguyên tắc đóng gói giúp đơn giản hóa nhiệm vụ lập trình theo nghĩa là không còn cần phải lo lắng về vô số dòng mã thực hiện công việc của lớp pen ở hậu trường. Tất cả những gì cần làm là tạo một thể hiện của lớp pen và tương tác với nó bằng cách gọi các hàm của nó.

Một trong khía cạnh quan trọngĐóng gói là bảo vệ dữ liệu. Lý tưởng nhất là dữ liệu mô tả trạng thái của một đối tượng phải được xác định là đóng và không thể truy cập được từ môi trường bên ngoài. Trong trường hợp này, môi trường bên ngoài của đối tượng sẽ buộc phải yêu cầu quyền thay đổi hoặc đọc các giá trị tương ứng. Vì vậy, khái niệm đóng gói phản ánh nguyên tắc chung, theo đó các trường dữ liệu của một đối tượng không thể truy cập trực tiếp từ giao diện mở. Nếu người dùng cần thay đổi trạng thái của một đối tượng thì anh ta phải thực hiện việc này không trực tiếp mà gián tiếp bằng cách sử dụng các hàm đọc ( lấy()) và sửa đổi ( bộ()). Trong C#, khả năng truy cập dữ liệu được triển khai ở cấp cú pháp bằng cách sử dụng từ khóa công cộng, riêng tư, được bảo vệ, Và được bảo vệ nội bộ.

Trong một đối tượng, mã, dữ liệu hoặc cả mã và dữ liệu có thể được đóng hoặc mở đối với các đối tượng khác. Mã và dữ liệu độc quyền được biết và có thể truy cập được từ một phần khác của đối tượng đó (tức là chỉ đối với chính đối tượng đó). Do đó để mã đóng và dữ liệu không thể được truy cập từ một phần của chương trình tồn tại bên ngoài đối tượng.

Nguyên tắc tiếp theo OOP là di sản , nghĩa là khả năng của ngôn ngữ trong việc cung cấp việc xây dựng các định nghĩa về các lớp mới dựa trên các định nghĩa của các lớp hiện có. Về bản chất, tính kế thừa cho phép bạn mở rộng hành vi của lớp cơ sở (còn gọi là lớp cha) bằng cách xây dựng một lớp con (gọi là phát sinh hoặc lớp trẻ), kế thừa đặc điểm và chức năng lớp cha. Về bản chất, hình thức thừa kế này là tái sử dụng mã chương trình của một lớp (cơ sở) trong các lớp khác (xuất phát từ nó). Thông thường, trong trường hợp này, lớp con mở rộng các khả năng của lớp cơ sở bằng cách thêm một số khả năng mới không có trong lớp cơ sở. lớp cơ sở. Hình thức kế thừa này được gọi là "is-a" (nghĩa là giống nhau nhưng có khả năng cao hơn).


Một hình thức tái sử dụng mã khác là mô hình bản địa hóa/ủy quyền (còn được gọi là mối quan hệ bản địa hóa "has-a"). Biểu mẫu này không được sử dụng để tạo mối quan hệ lớp-lớp con. Thay vào đó, một lớp có thể định nghĩa một số biến của một lớp khác trong chính nó và bộc lộ một số hoặc tất cả chức năng của nó với thế giới bên ngoài. Trong trường hợp này, lớp giống như một thùng chứa các thể hiện của các lớp khác.

Nguyên tắc thứ ba của OOP là tính đa hình . Nó đặc trưng cho khả năng của một ngôn ngữ trong việc diễn giải các đối tượng liên quan theo cùng một cách. Tính năng này của ngôn ngữ hướng đối tượng cho phép một lớp cơ sở xác định một tập hợp các thành viên cho tất cả các lớp dẫn xuất. Về mặt hình thức, thành viên chung này được gọi là giao diện đa hình. Giao diện đa hình cho một lớp được xây dựng bằng cách xác định một số số tùy ý ảotrừu tượng chức năng. Hàm ảo lớp học Có thể thay đổi trong lớp dẫn xuất và một hàm trừu tượng có thể được chỉ một ghi đè. Khi các lớp dẫn xuất ghi đè các hàm được xác định trong lớp cơ sở, về cơ bản chúng sẽ ghi đè cách chúng phản hồi một yêu cầu tương ứng. Ngoài khả năng ghi đè các hàm, ngôn ngữ C# còn cung cấp khả năng sử dụng một dạng đa hình khác - nạp chồng hàm. Quá tải nên được coi là cơ hội bổ sung phân biệt chức năng với cùng tên, khác biệt số tiền khác nhau hoặc loại đối số. Do đó, nạp chồng có thể được áp dụng không chỉ cho các hàm thành viên của lớp mà còn cho các hàm toàn cục.

Có lẽ một nửa số vị trí tuyển dụng (nếu không muốn nói là nhiều hơn) yêu cầu kiến ​​thức và hiểu biết về OOP. Vâng, phương pháp này chắc chắn đã thu hút nhiều lập trình viên! Thông thường, việc hiểu OOP đi kèm với kinh nghiệm, vì thực tế không có tài liệu nào phù hợp và dễ tiếp cận về chủ đề này. Và ngay cả nếu có thì cũng không chắc độc giả sẽ bắt gặp được chúng. Tôi hy vọng tôi có thể giải thích các nguyên tắc của phương pháp tuyệt vời này, như người ta nói, trên ngón tay của tôi.

Vì vậy, ở đầu bài viết tôi đã đề cập đến thuật ngữ “phương pháp luận”. Khi áp dụng vào lập trình, thuật ngữ này ngụ ý sự hiện diện của bất kỳ tập hợp cách nào để tổ chức mã, phương pháp viết mã, tuân theo đó, lập trình viên sẽ có thể viết các chương trình hoàn toàn có thể sử dụng được.

OOP (hoặc lập trình hướng đối tượng) là một cách tổ chức mã chương trình trong đó các khối xây dựng chính của chương trình là các đối tượng và lớp và logic của chương trình được xây dựng dựa trên sự tương tác giữa chúng.


Về đối tượng và lớp

Lớp học- đây là cấu trúc dữ liệu có thể do chính lập trình viên tạo ra. Theo thuật ngữ OOP, một lớp bao gồm lĩnh vực(nói một cách đơn giản - các biến) và phương pháp(nói một cách đơn giản - chức năng). Và hóa ra, sự kết hợp giữa dữ liệu và các chức năng để làm việc trên nó trong một cấu trúc mang lại sức mạnh không thể tưởng tượng được. Một đối tượng là một thể hiện cụ thể của một lớp. Tương tự như một lớp có cấu trúc dữ liệu, một đối tượng là một cấu trúc dữ liệu cụ thể có một số giá trị được gán cho các trường của nó. Hãy để tôi giải thích bằng một ví dụ:

Giả sử chúng ta cần viết một chương trình tính chu vi và diện tích của một hình tam giác, tính theo hai cạnh và góc giữa chúng. Để viết một chương trình như vậy bằng OOP, chúng ta sẽ cần tạo một lớp (tức là một cấu trúc) Tam giác. Lớp Triangle sẽ lưu trữ ba trường (ba biến): cạnh A, cạnh B, góc giữa chúng; và hai phương thức (hai hàm): tính chu vi, tính diện tích. Với lớp này, chúng ta có thể mô tả bất kỳ hình tam giác nào và tính chu vi và diện tích. Vì vậy, một tam giác cụ thể với các cạnh cụ thể và một góc giữa chúng sẽ được gọi là một thể hiện của lớp Tam giác. Do đó, một lớp là một mẫu và một thể hiện là một triển khai cụ thể của mẫu đó. Nhưng các thể hiện là các đối tượng, tức là các phần tử cụ thể lưu trữ các giá trị cụ thể.

Một trong những ngôn ngữ lập trình hướng đối tượng phổ biến nhất là java. Ở đó bạn đơn giản là không thể làm gì nếu không sử dụng đồ vật. Đây là mã của một lớp mô tả một hình tam giác trong ngôn ngữ này sẽ trông như thế nào:

/** * Lớp tam giác. */ class Triangle ( /** * Phương thức đặc biệt được gọi là hàm tạo lớp. * Lấy ba tham số làm đầu vào: * chiều dài cạnh A, chiều dài cạnh B, * góc giữa các cạnh này (tính bằng độ) */ Triangle(double sideA, double sideB , double angleAB) ( this.sideA = sideA; this.sideB = sideB; this.angleAB = gócAB; ) double sideA; //Trường lớp, lưu trữ giá trị của cạnh A trong tam giác double sideB được mô tả; tam giác góc đôiAB; //Trường lớp lưu trữ góc (tính bằng độ) giữa hai cạnh trong tam giác được mô tả /** * Phương thức lớp tính diện tích của tam giác */ double getSquare() ( double Square = this.sideA * this .sideB * Math.sin(this.angleAB * Math.PI / 180); /** * Lớp phương thức tính chu vi của một hình tam giác */ double getPerimeter() ( double sideC = Math.sqrt(Math.pow ) (this.sideA, 2) + Math.pow(this.sideB, 2) - 2 * this.sideA * this.sideB * Math.cos(this.angleAB * Math.PI / 180)); chu vi gấp đôi = this.sideA + this.sideB + sideC; chu vi trở lại; ) )

Nếu chúng ta thêm đoạn mã sau vào trong lớp:

/** * Đây là nơi chương trình chạy */ public static void main(String args) ( //Các giá trị 5, 17, 35 đi vào hàm tạo của lớp Triangle Triangle Triangle1 = new Triangle(5, 17, 35 ); System.out .println("Diện tích tam giác1: "+tam giác1.getSquare()); System.out.println("Chu vi của tam giác1: "+triangle1.getPerimeter()); , 8, 60 đi tới hàm tạo của lớp Triangle Triangle2 = new Triangle(6, 8, 60); System.out.println("Diện tích tam giác1: "+triangle2.getSquare()); "Chu vi của tam giác1: "+triangle2.getPerimeter());

thì chương trình đã có thể được khởi chạy để thực thi. Đây là một tính năng ngôn ngữ java. Nếu lớp có một phương thức như vậy

Khoảng trống tĩnh công khai chính(String args)

sau đó lớp này có thể được thực thi. Chúng ta hãy xem xét mã chi tiết hơn. Hãy bắt đầu với dòng

Tam giác tam giác1 = Tam giác mới(5, 17, 35);

Ở đây chúng ta tạo một thể hiện của Triangle1 của lớp Triangle và ngay lập tức cung cấp cho nó các tham số của các cạnh và góc giữa chúng. Đồng thời, một phương thức đặc biệt gọi là hàm tạo được gọi và điền vào các trường của đối tượng các giá trị được truyền cho hàm tạo. Vâng, còn các dòng thì sao?

System.out.println("Diện tích tam giác1: "+tam giác1.getSquare()); System.out.println("Chu vi của tam giác1: "+triangle1.getPerimeter());

xuất diện tích tính toán của hình tam giác và chu vi của nó ra bảng điều khiển.

Điều tương tự cũng xảy ra với phiên bản thứ hai của lớp Triangle.

Hiểu bản chất của các lớp và cách xây dựng các đối tượng cụ thể là bước đầu tiên tự tin để hiểu phương pháp OOP.

Một lần nữa, điều quan trọng nhất:

OOP- đây là cách tổ chức mã chương trình;

Lớp học- đây là cấu trúc dữ liệu tùy chỉnh tập hợp dữ liệu và hàm để làm việc với chúng (trường lớp và phương thức lớp);

Một đối tượng là một thể hiện cụ thể của một lớp có các trường được cung cấp các giá trị cụ thể.


Ba từ kỳ diệu

OOP bao gồm ba cách tiếp cận chính: kế thừa, đóng gói và đa hình. Để bắt đầu, tôi sẽ đưa ra định nghĩa từ wikipedia:

Đóng gói là một thuộc tính hệ thống cho phép bạn kết hợp dữ liệu và các phương thức hoạt động với chúng trong một lớp. Một số ngôn ngữ (ví dụ C++) đánh đồng việc đóng gói với việc ẩn, nhưng hầu hết (Smalltalk, Eiffel, OCaml) đều phân biệt giữa các khái niệm này.

Kế thừa là một thuộc tính hệ thống cho phép bạn mô tả lớp mới dựa trên cái hiện có với chức năng được mượn một phần hoặc toàn bộ. Lớp mà sự kế thừa được bắt nguồn từ đó được gọi là lớp cơ sở, lớp cha hoặc lớp cha. Một lớp mới là lớp con cháu, lớp thừa kế, lớp con hoặc lớp dẫn xuất.

Đa hình là một thuộc tính hệ thống cho phép bạn sử dụng các đối tượng có cùng giao diện mà không cần thông tin về loại và cấu trúc bên trong của đối tượng.

Việc hiểu ý nghĩa thật sự của tất cả những định nghĩa này là khá khó khăn. Trong những cuốn sách chuyên ngành đề cập đến chủ đề này, mỗi định nghĩa thường dành cả một chương nhưng ít nhất là một đoạn văn. Mặc dù vậy, bản chất những gì một lập trình viên cần hiểu và in sâu mãi trong đầu là rất ít.
Và để làm ví dụ cho việc phân tích, chúng ta sẽ sử dụng các số liệu trên mặt phẳng. Từ hình học phổ thông, chúng ta biết rằng đối với tất cả các hình vẽ trên mặt phẳng, đều có thể tính được chu vi và diện tích. Ví dụ: đối với một điểm cả hai tham số đều bằng 0. Đối với một đoạn, chúng ta chỉ có thể tính chu vi. Và đối với hình vuông, hình chữ nhật hoặc hình tam giác - cả hai. Bây giờ chúng tôi sẽ mô tả nhiệm vụ này theo thuật ngữ OOP. Bạn cũng nên nắm bắt chuỗi lý luận dẫn đến hệ thống phân cấp lớp, từ đó, được thể hiện trong mã làm việc. Đi:


Vậy điểm đó là nhỏ nhất hình hình học, là cơ sở của tất cả các công trình khác (hình). Vì vậy, điểm được chọn làm lớp cha cơ sở. Hãy viết một lớp điểm trong Java:

/** * Lớp điểm. Lớp cơ sở */ lớp Điểm ( /** * Hàm tạo trống */ Point() () /** * Phương thức lớp tính diện tích hình */ double getSquare() ( return 0; ) /** * Phương thức lớp tính chu vi của hình */ double getPerimeter() ( return 0; ) /** * Phương thức lớp trả về mô tả của hình */ String getDescription() ( return "Point"; ) )

Lớp Point kết quả có một hàm tạo trống vì trong ví dụ này Chúng tôi làm việc không có tọa độ cụ thể mà chỉ hoạt động với các tham số và giá trị phụ. Vì điểm không có cạnh nên không cần truyền bất kỳ tham số nào cho nó. Cũng lưu ý rằng lớp này có các phương thức Point::getSquare() và Point::getPerimeter() để tính diện tích và chu vi, cả hai đều trả về 0. Đối với một điểm, điều đó là hợp lý.


Vì điểm của chúng ta là cơ sở của tất cả các hình khác nên chúng ta kế thừa các lớp của các hình này từ lớp Point. Chúng ta hãy mô tả lớp của một đoạn được kế thừa từ lớp của một điểm:

/** * Phân đoạn dòng lớp */ lớp LineSegment mở rộng Điểm ( LineSegment(double SegLength) ( this.segmentLength = SegLength; ) double SegmentLength; // Độ dài của dòng /** * Phương thức lớp được ghi đè tính diện tích của ​dòng */ double getSquare( ) ( return 0; ) /** * Phương thức lớp được ghi đè tính chu vi của một đoạn */ double getPerimeter() ( return this.segmentLength; ) Chuỗi getDescription() ( return "Độ dài đoạn: " + this.segmentLength; ) )

Phân đoạn dòng lớp mở rộng điểm

nghĩa là lớp LineSegment kế thừa từ lớp Point. Các phương thức LineSegment::getSquare() và LineSegment::getPerimeter() ghi đè các phương thức lớp cơ sở tương ứng. Diện tích của một đoạn luôn bằng 0 và diện tích chu vi bằng chiều dài của đoạn này.

Bây giờ, giống như lớp phân đoạn, chúng ta sẽ mô tả lớp tam giác (cũng kế thừa từ lớp điểm):

/** * Lớp tam giác. */ lớp Tam giác mở rộng Điểm ( /** * Hàm tạo lớp. Lấy ba tham số làm đầu vào: * độ dài cạnh A, độ dài cạnh B, * góc giữa các cạnh này (tính bằng độ) */ Tam giác(hai cạnhA, hai cạnhB, double angleAB ) ( this.sideA = sideA; this.sideB = sideB; this.angleAB = gócAB; ) double sideA; //Trường lớp, lưu trữ giá trị của cạnh A trong tam giác double sideB được mô tả //Trường lớp, lưu trữ giá trị của cạnh B trong tam giác đôi gócAB được mô tả // Trường lớp lưu trữ góc (tính bằng độ) giữa hai cạnh trong tam giác được mô tả /** * Phương thức lớp tính diện tích của tam giác */ double getSquare() ( double Square = (this.sideA * this.sideB * Math.sin(this.angleAB * Math.PI / 180))/2; trả về hình vuông; /** * Phương thức lớp tính chu vi của một hình tam giác */ double getPerimeter() ( double sideC = Math.sqrt(Math. pow(this.sideA, 2) + Math.pow(this.sideB, 2) - 2 * this.sideA * this.sideB * Math.cos( this.angleAB * Math.PI/180)); chu vi gấp đôi = this.sideA + this.sideB + sideC; chu vi trở lại; ) Chuỗi getDescription() ( return "Tam giác có các cạnh: " + this.sideA + ", " + this.sideB + " và góc giữa chúng: " + this.angleAB; ) )

Không có gì mới ở đây cả. Ngoài ra, các phương thức Triangle::getSquare() và Triangle::getPerimeter() ghi đè các phương thức tương ứng của lớp cơ sở.
Trên thực tế, bây giờ, chính đoạn mã thể hiện sự kỳ diệu của tính đa hình và bộc lộ sức mạnh của OOP:

Class Main ( /** * Đây là nơi chương trình chạy */ public static void main(String args) ( //ArrayList - Đây là cấu trúc dữ liệu đặc biệt trong java // cho phép bạn lưu trữ các đối tượng thuộc một loại nhất định trong một array. ArrayList figure = new ArrayList (); //thêm ba đối tượng khác nhau vào mảng figure.add(new Point()); figure.add(new LineSegment(133));

Chúng ta đã tạo một mảng các đối tượng của lớp Point, và vì các lớp LineSegment và Triangle kế thừa từ lớp Point nên chúng ta có thể đặt chúng trong mảng này. Hóa ra là chúng ta có thể coi mỗi hình trong mảng figure là một đối tượng của lớp Point. Tính đa hình là như thế này: không biết các đối tượng trong mảng hình thuộc về lớp nào, nhưng vì tất cả các đối tượng bên trong mảng này thuộc về cùng một lớp cơ sở Point, nên tất cả các phương thức áp dụng cho lớp Point cũng có thể áp dụng được đến các lớp con cháu của nó.


Bây giờ về đóng gói. Việc chúng ta đặt các tham số của một hình và các phương thức tính diện tích và chu vi trong một lớp là sự đóng gói; chúng ta đã gói gọn các hình đó thành các lớp riêng biệt. Việc chúng ta sử dụng một phương thức đặc biệt trong lớp để tính chu vi là tính đóng gói; chúng ta đã gói gọn việc tính chu vi trong phương thức getPerimiter(). Nói cách khác, đóng gói đang che giấu việc triển khai (có lẽ là định nghĩa ngắn nhất, đồng thời, đầy đủ về khả năng đóng gói).


Mã ví dụ đầy đủ:

Nhập java.util.ArrayList; class Main ( /** * Đây là nơi chương trình chạy */ public static void main(String args) ( //ArrayList là một cấu trúc dữ liệu đặc biệt trong java // cho phép bạn lưu trữ các đối tượng thuộc một loại nhất định trong một mảng. ArrayList figure = new ArrayList (); //thêm ba đối tượng khác nhau vào mảng figure.add(new Point()); figure.add(new LineSegment(133));

thông tin chung

OOP là phong cách lập trình xuất hiện vào những năm 80 của thế kỷ 20. Không giống như các ngôn ngữ thủ tục, nơi dữ liệu và hướng dẫn xử lý tồn tại riêng biệt, trong lập trình hướng đối tượng, thông tin này được kết hợp thành một thực thể duy nhất.

Nguyên tắc cơ bản của OOP

Di sản

Nguyên tắc thứ hai của OOP, tính kế thừa, là khả năng của một lớp sử dụng các phương thức của lớp khác mà không lặp lại việc triển khai thực tế của chúng. Kế thừa cho phép bạn loại bỏ sự dư thừa trong mã nguồn.

Đa hình

Một nguyên tắc khác của OOP là tính đa hình. Công dụng của nó có nghĩa là để thao tác các đối tượng có mức độ phức tạp khác nhau, bạn có thể tạo một giao diện sẽ phản ứng khác nhau với các sự kiện, đồng thời thực hiện chính xác các nhiệm vụ được giao.

Ngôn ngữ OOP

Nguyên tắc OOP được sử dụng trong các ngôn ngữ lập trình phổ biến nhất như C++ và Java, trong đó một phần quan trọng của chương trình và ứng dụng được phát triển. Ngoài ra còn có các ngôn ngữ OOP ít được sử dụng hơn - Delphi, Object Pascal, Ruby và nhiều ngôn ngữ khác.

Những lời chỉ trích về OOP

Bất chấp những tuyên bố chủ yếu là tích cực đối với phương pháp này, các nguyên tắc của OOP thường bị chỉ trích. Giống như OOP, nó có nhược điểm.

Đầu tiên là độ khó của quá trình chuyển đổi. Sẽ mất khá nhiều thời gian để hiểu các nguyên tắc của OOP, đặc biệt đối với những người chỉ làm việc chặt chẽ với các ngôn ngữ lập trình thủ tục.

Thứ hai, nhược điểm là tài liệu phức tạp hơn, vì không chỉ cần mô tả các lớp và đối tượng mà còn cả các trường hợp cụ thể về việc triển khai chúng.

Thứ ba, tính phổ biến quá mức của các phương pháp có thể dẫn đến thực tế là nguồn và các chương trình đang được phát triển sẽ bị quá tải với các chức năng và khả năng không có nhu cầu trong trường hợp cụ thể này. Ngoài ra, họ lưu ý đến sự kém hiệu quả trong việc phân bổ bộ nhớ. Tuy nhiên, bất chấp ý kiến ​​​​của người khác, số lượng lập trình viên OOP không ngừng tăng lên và bản thân các ngôn ngữ cũng phát triển nhanh chóng.

Java là một ngôn ngữ hướng đối tượng. Điều này có nghĩa là bạn cần viết các chương trình Java theo phong cách hướng đối tượng. Và phong cách này dựa trên việc sử dụng các đối tượng và lớp trong chương trình. Chúng ta hãy thử, với sự trợ giúp của các ví dụ, để hiểu lớp và đối tượng là gì, cũng như cách áp dụng các nguyên tắc cơ bản của OOP trong thực tế: trừu tượng, kế thừa, đa hình và đóng gói.

Một đối tượng là gì?

Thế giới chúng ta đang sống bao gồm các đồ vật. Nếu nhìn xung quanh, chúng ta sẽ thấy xung quanh mình là nhà cửa, cây cối, ô tô, đồ đạc, bát đĩa, máy tính. Tất cả những vật phẩm này đều là đồ vật và mỗi vật phẩm có một tập hợp các đặc điểm, hành vi và mục đích cụ thể. Chúng ta đã quen với các đồ vật và luôn sử dụng chúng cho những mục đích rất cụ thể. Ví dụ, nếu chúng ta cần đi làm, chúng ta sử dụng ô tô, nếu muốn ăn, chúng ta dùng bát đĩa, và nếu cần thư giãn, chúng ta cần một chiếc ghế sofa thoải mái. Một người có thói quen suy nghĩ khách quan để giải quyết vấn đề trong Cuộc sống hàng ngày. Đây là một trong những lý do sử dụng đối tượng trong lập trình và cách tiếp cận tạo chương trình này được gọi là hướng đối tượng. Hãy đưa ra một ví dụ. Hãy tưởng tượng những gì bạn đã phát triển người mẫu mớiđiện thoại và muốn sửa nó sản xuất hàng loạt. Là một nhà thiết kế điện thoại, bạn biết nó dùng để làm gì, hoạt động như thế nào và bao gồm những bộ phận nào (vỏ, micrô, loa, dây, nút, v.v.). Tuy nhiên, chỉ có bạn mới biết cách kết nối những phần này. Tuy nhiên, bạn không có ý định tự mình sản xuất điện thoại; vì điều này mà bạn có cả một đội ngũ nhân viên. Để bạn không phải giải thích mỗi lần cách kết nối các bộ phận của điện thoại và để tất cả các điện thoại được sản xuất đều giống nhau, trước khi bắt đầu sản xuất chúng, bạn sẽ cần tạo một bản vẽ dưới dạng một mô tả cấu trúc của điện thoại. Trong OOP, mô tả, bản vẽ, sơ đồ hoặc mẫu như vậy được gọi là lớp, từ đó một đối tượng được tạo khi chương trình được thực thi. Lớp là mô tả về một đối tượng chưa được tạo, giống như một mẫu chung bao gồm các trường, phương thức và hàm tạo, và đối tượng là một thể hiện của lớp được tạo trên cơ sở mô tả này.

Trừu tượng

Bây giờ chúng ta hãy nghĩ về cách chúng ta có thể chuyển từ một đối tượng trong thế giới thực sang một đối tượng trong chương trình, lấy điện thoại làm ví dụ. Lịch sử của phương tiện liên lạc này đã hơn 100 năm và điện thoại hiện đại, không giống như người tiền nhiệm của nó từ thế kỷ 19, là một thiết bị phức tạp hơn nhiều. Khi sử dụng điện thoại, chúng ta không nghĩ về cấu trúc của nó và các quá trình diễn ra bên trong nó. Chúng tôi chỉ đơn giản sử dụng các chức năng do nhà phát triển điện thoại cung cấp - các nút hoặc màn hình cảm ứngđể chọn một số và thực hiện cuộc gọi. Một trong những giao diện điện thoại đầu tiên là một núm xoay để thực hiện cuộc gọi. Tất nhiên, điều này không thuận tiện lắm. Tuy nhiên, tay cầm đã thực hiện đúng chức năng của nó. Nếu bạn nhìn vào chiếc điện thoại hiện đại nhất và đầu tiên, bạn có thể xác định ngay những chi tiết quan trọng nhất đối với cả một thiết bị từ cuối thế kỷ 19 và một chiếc điện thoại thông minh cực kỳ hiện đại. Đây là thực hiện cuộc gọi (quay số) và nhận cuộc gọi. Về cơ bản, đây là thứ khiến một chiếc điện thoại trở thành một chiếc điện thoại chứ không phải thứ gì khác. Bây giờ chúng ta đã áp dụng nguyên tắc trong OOP - làm nổi bật nhất đặc điểm quan trọng và thông tin về đối tượng. Nguyên tắc này được gọi là trừu tượng. Tính trừu tượng trong OOP cũng có thể được định nghĩa là cách biểu diễn các phần tử của một vấn đề trong thế giới thực dưới dạng các đối tượng trong một chương trình. Tính trừu tượng luôn gắn liền với việc khái quát hóa một số thông tin về tính chất của đối tượng hoặc đối tượng, vì vậy việc chính là tách biệt thông tin có ý nghĩa từ mức không đáng kể trong bối cảnh vấn đề đang được giải quyết. Trong trường hợp này, có thể có nhiều mức độ trừu tượng. Hãy thử áp dụng nguyên tắc trừu tượng vào điện thoại của chúng ta. Đầu tiên, hãy điểm qua những loại điện thoại phổ biến nhất từ ​​​​đầu đến nay. Ví dụ: chúng có thể được biểu diễn dưới dạng sơ đồ như trong Hình 1. Bây giờ, bằng cách sử dụng tính trừu tượng, chúng ta có thể làm nổi bật các đối tượng trong hệ thống phân cấp này thông tin chung: một loại đối tượng trừu tượng chung - điện thoại, đặc điểm chungđiện thoại - năm tạo ra nó, và giao diện chung- Tất cả các điện thoại đều có khả năng nhận và gửi cuộc gọi. Đây là những gì nó trông giống như trong Java: lớp trừu tượng công khai Tóm tắtPhone ( riêng tư int năm; công khai Tóm tắtPhone (int năm) ( this . năm = năm; ) cuộc gọi void trừu tượng công khai (int đầu raNumber); vòng trống trừu tượng công khai (int inputNumber) ; ) Dựa trên lớp trừu tượng này, chúng ta sẽ có thể tạo các loại điện thoại mới trong chương trình bằng cách sử dụng các nguyên tắc Java OOP cơ bản khác mà chúng ta sẽ xem xét bên dưới.

đóng gói

Bằng cách sử dụng sự trừu tượng Chúng tôi đánh dấu tổng quan cho mọi đối tượng. Tuy nhiên, mỗi mẫu điện thoại đều mang tính cá nhân và có phần khác biệt so với các mẫu điện thoại khác. Làm thế nào chúng ta có thể vạch ra ranh giới trong chương trình và xác định cá tính này? Làm cách nào để đảm bảo rằng không ai trong số người dùng vô tình hoặc cố ý có thể làm hỏng điện thoại của chúng tôi hoặc cố gắng chuyển đổi mẫu này sang mẫu khác? Đối với thế giới đồ vật thực, câu trả lời rất rõ ràng: bạn cần đặt tất cả các bộ phận vào thân điện thoại. Rốt cuộc, nếu chúng ta không làm điều này và để tất cả phần bên trong của điện thoại cũng như dây kết nối chúng ra bên ngoài, chắc chắn sẽ có một người thử nghiệm tò mò muốn “cải thiện” hoạt động của điện thoại của chúng ta. Để loại trừ sự can thiệp đó vào thiết kế và hoạt động của một đối tượng, OOP sử dụng nguyên tắc đóng gói - một nguyên tắc khác nguyên tắc cơ bản OOP, trong đó các thuộc tính và hành vi của một đối tượng được kết hợp trong một lớp, việc triển khai nội bộ của đối tượng bị ẩn khỏi người dùng và giao diện chung được cung cấp để làm việc với đối tượng. Công việc của người lập trình là xác định những thuộc tính và phương thức nào sẽ có sẵn để truy cập mở và đó là cách triển khai nội bộ của đối tượng và không thể truy cập được để thay đổi.

Đóng gói và kiểm soát truy cập

Giả sử rằng trong quá trình sản xuất, thông tin về nó được khắc ở mặt sau điện thoại: năm sản xuất hoặc logo của công ty nhà sản xuất. Thông tin này mô tả khá cụ thể Mô hình này- tình trạng của anh ấy. Có thể nói rằng nhà phát triển điện thoại đã quan tâm đến tính bất biến của thông tin này - khó có ai có thể nghĩ đến việc xóa bản khắc. Trong thế giới Java, trạng thái của các đối tượng trong tương lai được mô tả trong một lớp bằng cách sử dụng các trường và hành vi của chúng được mô tả bằng các phương thức. Khả năng thay đổi trạng thái và hành vi được thực hiện bằng cách sử dụng công cụ sửa đổi quyền truy cập vào các trường và phương thức - riêng tư, được bảo vệ, công cộng , Và mặc định (địa chỉ mặc định). Ví dụ: chúng tôi đã quyết định rằng năm tạo, tên của nhà sản xuất điện thoại và một trong các phương thức thuộc về triển khai nội bộ của lớp và không thể thay đổi bởi các đối tượng khác trong chương trình. Bằng cách sử dụng mã, lớp có thể được mô tả như sau: public class SomePhone ( Private int Year; Private String company; public SomePhone (int Year, String company) ( this . Year = Year; this . company = company; ) Private void openConnection ( ) ( / /findComutator //openNewConnection... ) public void call () ( openConnection () ; System. out. println ("Gọi số") ; ) public void ring () ( System. out. println ("Ding -ding") ; ) ) Công cụ sửa đổi riêng tư làm cho các trường và phương thức của một lớp chỉ khả dụng trong lớp đó. Điều này có nghĩa là bạn có thể truy cập riêng tư các trường từ bên ngoài là không thể, cũng như không có cách nào để gọi riêng tư phương pháp. Việc ẩn quyền truy cập vào phương thức openConnection cũng cho phép chúng ta tự do thay đổi cách triển khai nội bộ của phương thức này, vì phương thức này được đảm bảo không được các đối tượng khác sử dụng và sẽ không làm gián đoạn công việc của chúng. Để làm việc với đối tượng của chúng tôi, chúng tôi để mở các phương thức gọi và đổ chuông bằng cách sử dụng công cụ sửa đổi công cộng . Việc cung cấp các phương thức công khai để làm việc với một đối tượng cũng là một phần của cơ chế đóng gói, vì nếu quyền truy cập vào một đối tượng bị từ chối hoàn toàn thì nó sẽ trở nên vô dụng.

Di sản

Hãy nhìn lại biểu đồ điện thoại. Bạn có thể thấy rằng nó đại diện cho một hệ thống phân cấp trong đó mô hình nằm bên dưới có tất cả các đặc điểm của các mô hình nằm cao hơn trên nhánh, cộng với đặc điểm của chính nó. Ví dụ: điện thoại thông minh sử dụng mạng di độngđể liên lạc (có đặc tính của điện thoại di động), không dây và di động (có đặc tính điện thoại không dây) và có thể nhận và thực hiện cuộc gọi (sử dụng thuộc tính điện thoại). Trong trường hợp này, chúng ta có thể nói về sự kế thừa các thuộc tính của đối tượng. Trong lập trình, kế thừa là việc sử dụng các lớp hiện có để định nghĩa các lớp mới. Hãy xem một ví dụ về việc tạo một lớp điện thoại thông minh bằng cách sử dụng tính kế thừa. Tất cả các điện thoại không dây đều hoạt động từ pin, có tuổi thọ nhất định tính bằng giờ. Vì vậy, hãy thêm thuộc tính này vào lớp điện thoại không dây: lớp trừu tượng công khai WirelessPhone mở rộng Tóm tắtPhone ( int giờ; public WirelessPhone (int năm, int giờ) ( super (năm); this . giờ = giờ; ) ) Điện thoại di động kế thừa các thuộc tính của điện thoại không dây, Chúng tôi cũng đã thêm cách triển khai các phương thức gọi và đổ chuông cho lớp này: public class CellPhone mở rộng WirelessPhone ( public CellPhone (int năm, int giờ) ( super (năm, giờ); ) @Override public void call ( int outNumber) ( System. out. println ("Số gọi " + outNumber) ; @Ghi đè vòng void công khai (int inputNumber) ( System. out. println ( "Một thuê bao đang gọi cho bạn"+ Số đầu vào); ) ) Và cuối cùng, lớp điện thoại thông minh, không giống như điện thoại di động cổ điển, có hệ điều hành hoàn chỉnh. Bạn có thể thêm các chương trình mới được thiết bị này hỗ trợ vào điện thoại thông minh của mình. hệ điều hành, do đó mở rộng chức năng của nó. Bằng cách sử dụng mã, lớp có thể được mô tả như sau: lớp công khai Điện thoại thông minh mở rộng Điện thoại di động ( riêng tư Chuỗi hoạt độngSystem; công khai Điện thoại thông minh (int năm, int giờ, Chuỗi hoạt độngHệ thống) ( super (năm, giờ); this . hoạt độngSystem = hoạt độngHệ thống; ) chung void install (Chương trình chuỗi) ( System. out. println ("Tôi cài đặt " + chương trình + "cho" + OperationSystem) ; ) ) Như bạn có thể thấy, chúng tôi đã tạo rất ít mã mới để mô tả lớp Điện thoại thông minh, nhưng chúng tôi có một mã mới lớp với chức năng mới. Sử dụng nguyên tắc OOP java này có thể làm giảm đáng kể số lượng mã và do đó giúp công việc của lập trình viên trở nên dễ dàng hơn.

Đa hình

Nếu chúng ta nhìn vào tất cả các kiểu điện thoại, thì mặc dù có sự khác biệt về hình dáng và thiết kế của các kiểu máy, nhưng chúng ta có thể xác định được một số điểm nhất định. hành vi chung– chúng đều có thể nhận và thực hiện cuộc gọi và có bộ nút điều khiển khá rõ ràng và đơn giản. Áp dụng một trong những nguyên tắc cơ bản của OOP mà chúng ta đã biết, trừu tượng hóa trong thuật ngữ lập trình, chúng ta có thể nói rằng đối tượng điện thoại có một giao diện chung. Vì vậy, người dùng điện thoại có thể khá thoải mái sử dụng các dòng máy khác nhau bằng cùng một nút điều khiển (cơ hoặc cảm ứng) mà không cần đi sâu vào chi tiết kỹ thuật của máy. Vì vậy, bạn thường xuyên sử dụng điện thoại di động và bạn có thể dễ dàng thực hiện cuộc gọi từ đối tác điện thoại cố định của nó. Một nguyên tắc trong OOP khi một chương trình có thể sử dụng các đối tượng có cùng giao diện mà không cần thông tin về cơ cấu nội bộđối tượng được gọi tính đa hình . Hãy tưởng tượng rằng trong chương trình của chúng ta, chúng ta cần mô tả một người dùng có thể sử dụng bất kỳ kiểu điện thoại nào để gọi cho người dùng khác. Đây là cách bạn có thể thực hiện: public class User ( Private String name; public User (String name) ( this . name = name; ) public void callAnotherUser (int number, Tóm tắtPhone phone) ( // đây là tính đa hình - sử dụng kiểu trừu tượng abstractPhone phone trong mã!điện thoại. gọi (số); ) ) ) Bây giờ hãy mô tả mô hình khác nhau những cái điện thoại. Một trong những mẫu điện thoại đầu tiên: public class ThomasEdisonPhone mở rộng Tóm tắtPhone ( public ThomasEdisonPhone (int Year) ( super (year) ; ) @Override public void call (int outNumber) ( System. out. println ("Xoay núm" ); System . out .println( "Xin vui lòng cung cấp số điện thoại của bạn, thưa ông"); ) @Override public void ring (int inputNumber) ( System. out. println ("Điện thoại đang đổ chuông") ; ) ) Bình thường điện thoại cố định: lớp công khai Điện thoại mở rộng Tóm tắtPhone ( Điện thoại công cộng (int năm) ( super (năm) ; ) @Ghi đè cuộc gọi void công khai (int đầu raNumber) ( System. out. println ("Số gọi" + số đầu ra) ; ) @Ghi đè vòng trống công khai (int inputNumber) ( System. out. println ("Điện thoại đang đổ chuông" ) ; ) ) Và cuối cùng, một chiếc điện thoại video thú vị: public class VideoPhone mở rộng Tóm tắtPhone ( public VideoPhone (int năm) ( super (year) ; ) @Override cuộc gọi void công khai (int đầu raNumber) ( System. out. println ( "Tôi đang kết nối kênh video cho người đăng ký"+ Số đầu ra); ) @Ghi đè vòng void công khai (int inputNumber) ( System. out. println ( "Bạn có một cuộc gọi video đến..."+ Số đầu vào); ) ) Hãy tạo các đối tượng trong phương thức main() và thử nghiệm phương thức callAnotherUser: Tóm tắtPhone firstPhone = new ThomasEdisonPhone (1879) ; Điện thoại Tóm tắtPhone = Điện thoại mới (1984); Tóm tắtPhone videoPhone= VideoPhone mới (2018); Người dùng người dùng = Người dùng mới ("Andrey" ); người dùng. callAnotherUser(224466, firstPhone); // Xoay núm //Xin vui lòng cung cấp số thuê bao ạ người dùng. callAnotherUser(224466, điện thoại); //Gọi tới số 224466 người dùng. callAnotherUser(224466, videoPhone); //Kết nối kênh video cho thuê bao 224466 Bằng cách gọi cùng một phương thức trên đối tượng người dùng, chúng tôi nhận được các kết quả khác nhau. Việc lựa chọn cách triển khai cụ thể của phương thức gọi bên trong phương thức callAnotherUser được thực hiện linh hoạt dựa trên loại cụ thểđối tượng gọi trong quá trình thực hiện chương trình. Đây là ưu điểm chính của tính đa hình - sự lựa chọn triển khai trong quá trình thực hiện chương trình. Trong các ví dụ về lớp điện thoại ở trên, chúng tôi đã sử dụng ghi đè phương thức, một kỹ thuật thay đổi cách triển khai phương thức được xác định trong lớp cơ sở mà không thay đổi chữ ký phương thức. Về bản chất, đây là sự thay thế cho phương pháp này và nó phương pháp mới, được định nghĩa trong một lớp con, được gọi khi chương trình thực thi. Thông thường, khi ghi đè một phương thức, chú thích @Override được sử dụng để báo cho trình biên dịch kiểm tra chữ ký của các phương thức bị ghi đè và ghi đè. Sau cùng Để đảm bảo rằng phong cách chương trình của bạn phù hợp với khái niệm OOP và các nguyên tắc của OOP java, hãy làm theo các mẹo sau:
  • làm nổi bật các đặc điểm chính của đối tượng;
  • làm nổi bật các thuộc tính và hành vi chung và sử dụng tính kế thừa khi tạo đối tượng;
  • sử dụng các kiểu trừu tượng để mô tả đối tượng;
  • Cố gắng luôn ẩn các phương thức và trường liên quan đến việc triển khai nội bộ của lớp.