1. Đối tượng và lớp

Hôm nay bạn sẽ tìm hiểu một chút về cách thức hoạt động của một chương trình Java điển hình. Đây là tin quan trọng: mọi chương trình Java đều bao gồm các lớp và đối tượng.

Bạn đã biết lớp là gì, nhưng đối tượng là gì?

Tôi sẽ bắt đầu với một phép loại suy. Hãy tưởng tượng bạn muốn làm một con tàu nhỏ. Trước tiên, bạn cần tạo một bản thiết kế và sau đó đưa nó cho nhà máy, nơi một con tàu sẽ được đóng theo bản thiết kế. Hoặc có lẽ hàng tá. Hoặc bao nhiêu tàu tùy thích. Hàng chục con tàu giống hệt nhau được đóng theo một bản thiết kế duy nhất. Đó là điều quan trọng ở đây.

Lập trình Java cũng vậy.

bản thiết kế

Một lập trình viên giống như một nhà thiết kế. Một nhà thiết kế tạo ra các bản thiết kế và một lập trình viên Java viết các lớp. Các bộ phận được tạo dựa trên bản thiết kế và các đối tượng được tạo dựa trên các lớp.

Đầu tiên, chúng ta viết các lớp (tạo bản thiết kế), và sau đó, khi chương trình chạy, máy Java sẽ tạo các đối tượng dựa trên các lớp này. Theo cách tương tự mà các con tàu được tạo ra từ bản thiết kế.

Chỉ có một bản thiết kế, nhưng có thể có nhiều tàu. Các con tàu là khác biệt - chúng có tên khác nhau và chở các loại hàng hóa khác nhau. Nhưng chúng rất giống nhau: tất cả đều có chung một thiết kế và có thể thực hiện các tác vụ tương tự nhau.

Hoặc đây là một sự tương tự khác ...

Tổ kiến

Tổ kiến ​​là một ví dụ điển hình về cách các đối tượng tương tác. Có ba loại kiến ​​trong một tổ kiến ​​đơn giản: kiến ​​chúa, kiến ​​lính và kiến ​​thợ.

Số lượng kiến ​​của mỗi lớp là khác nhau. Chỉ có một kiến ​​chúa duy nhất cho toàn bộ tổ kiến, nhưng có hàng tá kiến ​​lính và hàng trăm kiến ​​thợ. Ba lớp và hàng trăm đối tượng. Kiến tương tác với nhau — với những con kiến ​​cùng lớp và với những con kiến ​​khác lớp — theo những quy tắc cứng nhắc.

Đây là ví dụ hoàn hảo. Mọi thứ diễn ra chính xác như thế này trong một chương trình điển hình. Có một đối tượng chính tạo ra các đối tượng của tất cả các lớp khác. Các đối tượng bắt đầu tương tác với nhau và với "thế giới bên ngoài" của chương trình. Hành vi của các đối tượng được mã hóa cứng bên trong.

Hai phép loại suy này là hai mặt của cùng một đồng tiền. Sự thật là ở giữa. Ví dụ đầu tiên (về bản thiết kế và con tàu) cho thấy mối quan hệ giữa một lớp và các đối tượng của lớp đó. Đây là một phép loại suy mạnh mẽ. Ví dụ thứ hai (về một tổ kiến) cho thấy mối quan hệ giữa các lớp được viết và các đối tượng tồn tại khi chương trình chạy.

Trước tiên, bạn phải viết các lớp cho mọi đối tượng sẽ tồn tại trong chương trình, sau đó cũng mô tả cách chúng tương tác. Vâng, đúng vậy, nhưng nó dễ hơn âm thanh.

Trong Java, tất cả các thực thể đều là các đối tượng trong thời gian chạy và viết chương trình là mô tả các cách khác nhau mà các đối tượng tương tác. Các đối tượng chỉ cần gọi các phương thức của nhau và truyền dữ liệu cần thiết cho chúng.

Tài liệu

Và làm thế nào để bạn biết dữ liệu nào sẽ chuyển đến các phương thức? Những người đến trước bạn nghĩ về mọi thứ.

Mỗi lớp thường có một mô tả cho biết nó được tạo ra để làm gì. Ngoài ra, mỗi phương thức công khai thường có một mô tả cho biết nó làm gì và dữ liệu nào cần được truyền cho nó.

Để sử dụng một lớp, bạn cần có một ý tưởng chung về những gì nó làm. Và bạn cần biết chính xác tác dụng của từng phương pháp. Nhưng bạn không cần phải biết làm thế nào nó làm điều đó. Nó giống như một cây đũa thần.

Hãy xem mã để sao chép một tệp:

Sao chép tệp c:\\data.txt sang tệp c:\\result.txt
package com.codegym.lesson2;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;

public class FileCopy
{
   public static void main(String[] args) throws IOException
   {
      FileInputStream fileInputStream = new FileInputStream("c:\\data.txt");
      FileOutputStream fileOutputStream = new FileOutputStream("c:\\result.txt");

      while (fileInputStream.available() > 0)
      {
         int data = fileInputStream.read();
         fileOutputStream.write(data);
      }

      fileInputStream.close();
      fileOutputStream.close();
   }
}

Nếu bạn đọc từng dòng mã này, bạn có thể đoán những gì nó làm theo thuật ngữ chung. Mặc dù điều đó cần kinh nghiệm và thực hành. Sau một thời gian, mã này sẽ có vẻ quen thuộc và dễ hiểu đối với bạn.


2. Thiết kế chương trình

Thiết kế chương trình là cả một nghệ thuật. Nó đồng thời đơn giản và khó khăn. Đơn giản, bởi vì không có luật nghiêm ngặt: bất cứ điều gì không bị cấm đều được phép. Chà, và đó cũng là điều gây khó khăn: có rất nhiều cách để làm một việc gì đó và không dễ để tìm ra cách tốt nhất.

Thiết kế một chương trình giống như viết một cuốn sách. Một mặt, bạn chỉ cần viết các chữ cái, từ và câu. Mặt khác, cốt truyện, nhân vật, mâu thuẫn, xung đột nội tâm, phong cách kể chuyện, âm mưu, v.v.

Điều chính là phải hiểu bạn đang viết mã cho ai. Và bạn viết mã cho các lập trình viên khác .

Phát triển sản phẩm chắc chắn có nghĩa là thực hiện các thay đổi: thứ gì đó được thêm vào ở đây, thứ khác bị loại bỏ ở kia, thứ gì đó được thiết kế lại. Đó là cách các dự án lớn, to lớn và khổng lồ được sinh ra từ các bước lặp nhỏ.

Điều quan trọng nhất đối với mã là nó phải dễ hiểu đối với các lập trình viên khác. Mã không chính xác có thể hiểu được có thể được sửa chữa. Mã chính xác nhưng không thể hiểu được không thể được cải thiện.  Tất cả những gì bạn có thể làm là loại bỏ nó.

Vì vậy, làm thế nào để bạn viết mã tốt, sạch?

Làm điều này đòi hỏi ba điều:

  • Viết mã tốt và dễ hiểu bên trong các phương thức — đây là yêu cầu dễ dàng nhất
  • Quyết định thực thể nào nên được đưa vào chương trình
  • Tách chương trình thành các phần hợp lý một cách chính xác

Đằng sau những khái niệm này là gì?

Viết mã tốt bên trong các phương thức

Nếu bạn thậm chí có kỹ năng tiếng Anh cơ bản, bạn có thể nhận thấy đôi khi việc đọc mã dưới dạng câu tiếng Anh dễ dàng như thế nào:

  • class Cat extends Pet— Điều này có nghĩa là lớp Cat mở rộng lớp Pet
  • while(stream.ready())— miễn là luồng đã sẵn sàng...
  • if (a<b) return a; else return b— nếu anhỏ hơn bthì trả về, anếu không thì trả về b.

Đây là cố ý. Java là một trong số các ngôn ngữ giúp dễ dàng viết mã tự ghi lại, nghĩa là mã có thể hiểu được mà không cần bình luận. Trong mã Java tốt, nhiều phương thức đọc giống như câu tiếng Anh.

Khi viết mã, nhiệm vụ của bạn là làm cho nó đơn giản và ngắn gọn nhất có thể. Chỉ cần nghĩ xem mã của bạn có dễ đọc hay không và bạn sẽ bắt đầu đi đúng hướng.

Trong Java, người ta thường viết mã dễ đọc. Tốt nhất là tất cả mã cho một phương thức sẽ vừa trên một màn hình (tức là 20-30 dòng). Đây là tiêu chuẩn cho toàn bộ cộng đồng Java. Nếu mã có thể được cải thiện, nó sẽ được cải thiện.

Cách tốt nhất để học cách viết mã tốt là thông qua thực hành. Viết nhiều mã, nghiên cứu mã của người khác và nhờ các đồng nghiệp có kinh nghiệm hơn xem xét mã của bạn.

Và hãy nhớ rằng thời điểm bạn tự nói với bản thân "hãy để yên là đủ", sự phát triển của bạn sẽ dừng lại.

Quyết định thực thể nào nên được đưa vào chương trình

Bạn cần viết mã mà các lập trình viên khác có thể hiểu được. Nếu 9 trong số 10 lập trình viên sẽ bao gồm các lớp A, B và C trong thiết kế chương trình, thì bạn cũng nên tạo các lớp A, B và C trong chương trình của mình. Bạn phải viết mã mà người khác có thể hiểu được.

Tuyệt vời, hiệu quả, nhanh chóng, nhưng mã không chuẩn là mã xấu.

Bạn cần nghiên cứu các dự án của người khác: đây là cách tốt nhất, nhanh nhất và dễ dàng nhất để tiếp thu tất cả sự khôn ngoan đã tích lũy được trong ngành CNTT trong nhiều thập kỷ.

Và nhân tiện, bạn đã có quyền truy cập vào một dự án xuất sắc, phổ biến và được ghi chép đầy đủ — Java SDK . Bắt đầu với nó.

Phân tích các lớp học và cách chúng được tổ chức. Hãy suy nghĩ về lý do tại sao một số phương pháp là tĩnh và những phương pháp khác thì không. Tại sao các phương thức có các tham số cụ thể mà chúng có mà không phải các phương thức khác. Tại sao chính xác các phương thức này và tại sao các lớp được đặt tên theo tên của chúng và tại sao chúng được chứa trong các gói cụ thể của chúng.

Khi bạn bắt đầu hiểu câu trả lời cho tất cả những câu hỏi này, bạn sẽ có thể viết mã mà người khác có thể hiểu được.

Điều đó nói rằng, tôi muốn cảnh báo bạn không nên phân tích mã theo các phương thức của SDK Java. Nhiều phương pháp đã được viết lại để tối đa hóa tốc độ và khả năng đọc của chúng vẫn còn nhiều nghi vấn.

Tách chương trình thành các phần hợp lý một cách chính xác

Gần như mọi chương trình đều được chia thành các phần hoặc mô-đun. Mỗi phần chịu trách nhiệm về khía cạnh riêng của chương trình.

Máy tính có bo mạch chủ, màn hình và bàn phím — đây đều là những bộ phận riêng biệt, được ghép nối lỏng lẻo. Hơn nữa, chúng tương tác theo những cách tiêu chuẩn hóa: USB, HDMI, v.v. Nếu làm đổ cà phê lên bàn phím, bạn chỉ cần rửa sạch trong bồn rửa, để khô và sau đó tiếp tục sử dụng.

Nhưng máy tính xách tay là một ví dụ về kiến ​​trúc nguyên khối: có vẻ như chúng ta có thể phân biệt các phần logic riêng biệt, nhưng chúng được tích hợp nhiều hơn. Trên MacBookPro, bạn phải tháo rời một nửa máy tính xách tay để vệ sinh bàn phím. Và làm đổ cà phê của bạn lên máy tính xách tay là một lý do để đặt mua một chiếc máy tính xách tay mới. Không phải là một tách cà phê mới.


3. Tạo lớp học của riêng bạn

Nhưng vì bạn chỉ mới học lập trình, bạn nên bắt đầu từ việc học cách tạo các lớp học của riêng mình.

Tất nhiên, bạn đã tạo các lớp, nhưng bạn cần học để hiểu những lớp nào nên được đưa vào một chương trình, cách đặt tên cho chúng và chúng nên có những phương thức nào. Và làm thế nào họ nên tương tác với nhau.

Danh sách các thực thể

Nếu bạn không biết bắt đầu từ đâu, hãy bắt đầu lại từ đầu.

Khi bạn lần đầu tiên bắt đầu thiết kế một chương trình, bạn chỉ cần lấy một tờ giấy và viết ra danh sách các thực thể (đối tượng) nên có trong chương trình. Và sau đó viết mã theo nguyên tắc mỗi thực thể là một lớp riêng biệt.

Ví dụ

Giả sử bạn muốn viết một ván cờ vua. Bạn sẽ cần những thực thể sau: một bàn cờ và 6 loại quân cờ. Các mảnh di chuyển theo những cách khác nhau và có giá trị khác nhau. Nó có ý nghĩa rằng chúng là các lớp riêng biệt. Thật vậy, khi bạn mới bắt đầu, càng nhiều lớp càng tốt.

Rất hiếm khi gặp một lập trình viên mới viết mười lớp thay vì hai lớp. Thay vì viết mười lớp, những người mới bắt đầu thích viết hai lớp hoặc có lẽ chỉ một lớp. Vì vậy, hãy viết nhiều lớp hơn, các lập trình viên đồng nghiệp của tôi. Và mã của bạn sẽ trở nên rõ ràng hơn với mọi người, có lẽ ngoại trừ bạn 😛

Cờ vua

Giả sử chúng ta quyết định viết các lớp học cờ vua: những lớp học này trông như thế nào?

Có phải bàn cờ chỉ là một mảng 8 x 8? Tốt hơn là tạo một lớp riêng lưu trữ bên trong một tham chiếu đến một mảng. Sau đó, bạn có thể thêm nhiều phương thức hữu ích vào lớp "bàn cờ", chẳng hạn, để kiểm tra xem một ô cụ thể có trống hay bị chiếm dụng hay không

Nói chung, khi bạn bắt đầu, hãy luôn tuân theo nguyên tắc này: Một chương trình có nhiều thực thể khác nhau và một thực thể có một loại. Loại này là lớp.


4. Các biến và phương thức tĩnh

Cũng đừng quên sử dụng các biến và phương thức tĩnh. Nếu bạn có một quân cờ tương tác với một quân cờ khác trên bàn cờ, thì mã của bạn cần một phương thức tham chiếu đến quân thứ nhất và quân thứ hai cũng như bàn cờ.

Các biến tĩnh, có thể được truy cập từ bất kỳ đâu trong chương trình, thường được sử dụng để tránh liên tục chuyển các tham chiếu đến các đối tượng "luôn tồn tại".

Ví dụ, như thế này:

Mã số Ghi chú
public class ChessBoard
{
   public static ChessBoard board = new ChessBoard();
   public ChessItem[][] cells = new ChessItem[8][8];
   ...
}

public class Game
{
   public static void main(String[] args)
   {
      var board = ChessBoard.board;
      board.cells[0][3] = new King(Color.WHITE);
      board.cells[0][4] = new Queen(Color.WHITE);
      ...
   }
}


Một tham chiếu đến một ChessBoardđối tượng duy nhất.
Mảng hai chiều 8x8, không phải biến tĩnh.








Thêm các mảnh vào bảng.

Hoặc thay vì một biến tĩnh, bạn có thể tạo một phương thức trả về một đối tượng đơn lẻ. Ví dụ, như thế này:

public class ChessBoard
{
   private static ChessBoard board = new ChessBoard();
   public static ChessBoard getBoard()
   {
      return board;
   }

   public ChessItem[][] cells = new ChessItem[8][8];
   ...
}

public class Game
{
   public static void main(String[] args)
   {
      var board = ChessBoard.getBoard();
      board.cells[0][3] = new King(Color.WHITE);
      board.cells[0][4] = new Queen(Color.WHITE);
      ...
   }
}