+3

Những kiến thức Java Core PHẢI BIẾT để bắt đầu làm Automation Test (Part 2)

Bài viết trước đã giới thiệu tới mọi người những khái niệm cơ bản về Java như Đối tượng, Lớp đối tượng, các tính chất trong Java và các cấu trúc lệnh tiêu biểu trong Java, bài viết này sẽ giới thiệu tới các khái niệm cũng rất quan trọng và cần phải biết trong Java để có thể bắt tay vào làm Automation Test bao gồm các khái niệm liên quan tới astract, interface và multi thread...

I. Kế thừa bội

Trong Java không hỗ trợ đa kế thừa trực tiếp nhằm tránh các nhập nhằng của tính chất đa kế thừa từ C++, nhưng Java cho phép cài đặt nhiều giao tiếp (Interface) để thừa hưởng thêm các thuộc tính và phương thức của giao tiếp đó.

1. Giao tiếp (Interface)

a. Khai báo Interface Cú pháp: [public] interface <tên interface> [extends <danh sách interface>]

  • Tính chất: giao tiếp luôn public, nếu không khai báo tường minh thì giá trị mặc định của nó cũng được hiểu ngầm là public.
  • Tên interface: tên giao tiếp tuân thủ theo quy tắc đặt tên trong java.
  • Danh sách Interface: danh sách các giao tiếp cha đã được định nghĩa để kế thừa, mỗi giao tiếp cách nhau bởi dấy phẩy. phần trong ngoặc "[ ]" là tùy chọn.

Lưu ý: Một giao tiếp chỉ có thể kế thừa từ các giao tiếp khác mà không thể được kế thừa từ các lớp sẵn có. Ví dụ một giao tiếp không kế thừa từ bất kì giao tiếp nào

public interface Person {

}

Một giao tiếp có thể kế thừa một giao tiếp khác, interface khi không được khai báo tường minh được hiểu ngầm là public

interface Student extends Person{

}

Lớp có sẵn không thể kế thừa interface

public class Controller extends Person {
}

=> Sai, dòng code trên sẽ báo lỗi và suggest " Change 'Controller' to interface " hoặc " Change 'extends' to "implements' "

b. Khai báo phương thức của Interface Cú pháp: [public] <kiểu giá trị trả về> <tên phương thức> ([các tham số]) [throws <danh sách ngoại lệ>];

  • Tính chất: thuộc tính hay phương thức của interface luôn là public. Nếu không khai báo tường mình thì giá trị mặc định cũng là public. Đối với thuộc tính, thì luôn phải thêm là hằng (final) và tĩnh (static).
  • Kiểu giá trị trả về: có thể là các kiểu cơ bản của java, cũng có thể là kiểu do người dùng tự định nghĩa (kiểu đối tượng).
  • Tên phương thức: tuân thủ theo nguyên tắc đặt tên phương thức của lớp.
  • Các tham số: nếu có sẽ được xác định bằng một cặp <kiểu tham số> <tên tham số>. Các tham số được phân cách với nhau bằng dấu phẩy.
  • Các ngoại lệ: nếu có, mỗi ngoại lệ được phân cách bởi dấy phẩy.

Lưu ý:

  • Các phương thức của interface chỉ được khai báo dưới dạng mẫu mà không có cài đặt chi tiết, tức là có dấu chấm phẩy ngay sau khai báo và không có phần cài đặt trong dấu "{ }". Phần cài đặt chi tiết của các phương thức chỉ được thực hiện trong các lớp sử dụng interface đó.
  • Các thuộc tính của interface luôn có tính chất là hằng (final), tĩnh (static) và public. Do đó cần gán giá trị khởi đầu ngay khi khai báo thuộc tính của interface.

Ví dụ: khai báo một interface Product có một thuộc tính lưu nhãn hiệu của nhà sản xuất sản phẩm và một phương thức dùng để truy xuất giá bán của sản phẩm, thông thường tên thuộc tính khai báo dạng hằng (final) và tĩnh (static) thường để dạng in hoa, trong ví dụ dưới đây là 'MAKER'.

public interface Product {

	public static final String MAKER = "Adidas";

	public float getCost();

}

2. Cách sử dụng Interface

Interface chỉ được khai báo dưới dạng các phương thức mẫu và các thuộc tính hằng nên việc sử dụng interface phải thông qua một lớp có cài đặt interface đó. Việc khai báo một lớp cài đặt interface được thực hiện thông qua từ khóa implements như sau: <tính chất> class <tên lớp> implements <danh sách interface>

  • Tính chất và tên lớp: được khai báo như trong khai báo lớp thông thường.
  • Các interface: một lớp có thể cài đặt nhiều interface. Các interface được phân cách nhau bởi dấu phẩy. Khi đó, lớp phải cài đặt của thể tất cả các phương thức của tất cả các interface mà nó sử dụng.

Lưu ý:

  • Một phương thức đã khai báo trong interface bắt buộc phải được cài đặt cụ thể trong lớp cài đặt interface nhưng không được phép khai báo chồng. Nghĩa là số lượng các tham số của phương thức trong interface phải được giữ nguyên khi cài đặt cụ thể trong lớp.

Ví dụ: Cài đặt lớp giày (Shoe) cài đặt giao tiếp Product

public class Shoe implements Product {

	@Override
	public float getCost() {
		return 300f;
	}

	public String getMaker() {
		return MAKER;
	}

	public static void main(String[] args) {
		Shoe shoe = new Shoe();
		System.out.println("This Shoe of " + shoe.getMaker() + " having price " + shoe.getCost()+"$");
	}
}

Kết quả khi run chương trình trên sẽ in ra console dòng " This Shoe of Adidas having price 300.0$ ". Hàm getMaker() trẻ về nhãn hiệu nhà sản xuất của sản phẩm, là thuộc tính đã được khai báo trong interface. Hàm getCost() là cài đặt của lớp Shoe đối với phương thức đã được khai báo trong interface Product mà nó sử dụng, cài đặt này trả về 300.0.

II. Lớp trừu tượng

Lớp trừu tượng là dạng lớp trong đó các phương thức chỉ được khai báo ở dạng khuôn mẫu mà không được cài đặt chi tiết. Việc cài đặt chi tiết các phương thức chỉ được thực hiện ở các lớp con kế thừa lớp trừu tượng đó.

Lớp trừu tượng được sử dụng khi muốn định nghĩa một lớp mà không thể biết và định nghĩa ngay được các thuộc tính và phương thức của nó.

1. Khai báo

a. Khai báo lớp trừu tượng Cú pháp: [pubic] abstract class <tên lớp> { }

  • Tính chất: mặc định là public, bắt buộc phải có từ khóa abstract để xác định đây là lớp trừu tượng.
  • Tên lớp: tuân thủ theo cách đặt tên lớp thông thường của java.

Lưu ý: Lớp trừu tượng có thể kế thừa một lớp khác, nhưng lớp cha cũng phải là lớp trừu tượng. Ví dụ:

abstract class Product
{
}

b. Khai báo phương thức của lớp trừu tượng Tất cả các phương thức và thuộc tính của lớp trừu tượng đều phải khai báo là trừu tượng. Hơn nữa, các phương thức lớp trừu tượng chỉ được khai báo ở dạng khuôn mẫu mà không có phần cài đặt chi tiết. Cú pháp: [public] abstract <kiểu dữ liệu trả về> <tên phương thức> ([<các tham số>]) [throws <các ngoại lệ>];

  • Tính chất: mặc định là public
  • Kiểu dữ liệu trả về: có thể là kiểu cơ bản của Java, cũng có thể là kiểu do người dùng tự định nghia (kiểu đối tượng).
  • Tên phương thức: tuân theo quy tắc đặt tên phương thức trong java.
  • Các tham số: nếu có sẽ được xác định bằng một cặp <kiểu tham số> <tên tham số>. Các tham số được phân cách nhau bởi dấu phẩy.
  • Các ngoại lệ: nếu có thì mỗi ngoại lệ được phân cách nhau bởi dấu phẩy.

Lưu ý:

  • Phương thức trừu tượng không được là private hoặc static. Vì phương thức trừu tượng chỉ được khai báo chi tiết (nạp chồng) trong các lớp dẫn xuất (lớp kế thừa). Do đó , nếu phương thức là private thì không thể nạp chồng, nếu phương thức là static thì không thể thay đổi trong lớp dẫn xuất.
  • Phương thức trừu tượng chỉ được khai báo dưới dạng khuôn mẫu nê không có phần dấu móc "{ }" mà kết thúc bằng dấu chấm phẩy ";". Ví dụ:
abstract class Product {
	abstract String getMaker();
	abstract float getCost();
}

2. Sử dụng lớp trừu tượng

Lớp trừu tượng được sử dụng thông qua các lớp dẫn xuất của nó, các lớp kế thừa lớp trừu tượng, sử dụng từ khóa "extends" như cách kế thừa các lớp thông thường. Ví dụ: Khai báo một lớp Shoe kế thừa từ lớp Product. Lớp này cài đặt chi tiết 2 phương thức đã được khai báo trong Product như sau:

public class Shoe extends Product{

	@Override
	String getMaker() {
		return "Adidas";
	}

	@Override
	float getCost() {
		return 300;
	}
}

Khai báo một lớp Sandal kế thừa từ lớp Product. Lớp này cài đặt chi tiết 2 phương thức đã được khai báo trong Product như sau:

public class Sandal extends Product{

	@Override
	String getMaker() {
		return "Biti's";
	}

	@Override
	float getCost() {
		return 50;
	}

}

Sử dụng lại 2 lớp Shoe và Sandal, run chương trình sau:

public class AbstractDemo {
	public static void main(String[] args) {
		Shoe shoe = new Shoe();
		Sandal sandal = new Sandal();
		System.out.println("This Shoe of " + shoe.getMaker() + " having price " + shoe.getCost() + "$");
		System.out.println("This Sandal of " + sandal.getMaker() + " having price " + sandal.getCost() + "$");
	}

}

Kết quả in ra trên console:

This Shoe of Adidas having price 300.0$
This Sandal of Biti's having price 50.0$

Vậy khi nào nên sử dụng abstract, khi nào nên sử dụng interface, sự khác nhau giữa abstract và interface?

Trả lời cho câu hỏi này, bạn có thể tìm thấy rất nhiều bài viết trên mạng, ở bài viết này mình tạm không đề cập tới vấn đề, mọi người có thể đọc tham khảo một bài viblo theo đường dẫn sau: https://viblo.asia/p/khac-biet-giua-abstract-class-va-interface-trong-java-DzVkpgRXenW

III. MultiThreading

Java là một ngôn ngữ chương trình đa luồng (multithreaded), nghĩa là chúng ta có thể phát triển chương trình đa luồng sử dụng Java. Một chương trình đa luồng chứa hai hoặc nhiều phần mà có thể chạy đồng thời và mỗi phần có thể xử lý tác vụ khác nhau tại cùng một thời điểm, để sử dụng tốt nhất các nguồn có sẵn, đặc biệt khi máy tính của bạn có nhiều CPU.

Theo định nghĩa, đa nhiệm (multitasking) là khi nhiều tiến trình chia sẻ các tài nguyên xử lý chung ví dụ như một CPU. Multi-threading kế thừa ý tưởng của đa nhiệm trong các ứng dụng để có thể chia nhỏ các hoạt động riêng biệt bên trong một ứng dụng đơn thành các luồng riêng lẻ. Mỗi một thread có thể chạy song song. OS phân chia thời gian xử lý không chỉ trong các ứng dụng khác nhau, mà còn trong mỗi luồng bên trong một ứng dụng.

Multi-threading cho bạn khả năng viết một chương trình mà có nhiều hoạt động có thể thực thi đồng thời.

1. Vòng đời của 1 Thread

Các trạng thái của vòng đời:

  • New: Một thread mới bắt đầu vòng đời của nó ở trạng thái new. Nó duy trì trạng thái này cho tới khi chương trình chạy thread. Trạng thái này được xem như trạng thái sinh thread.
  • Runnable: Sau khi một thread mới sinh ra được start, thread chuyển sang trạng thái runnable. Một thread trong trạng thái này được xem như trạng thái thực thi tác vụ.
  • Waiting: Đôi khi thread chuyển sang trạng này khi phải đợi cho thread khác thực thi tác vụ của nó. Một thread chỉ chuyển về trang thái Runnable khi thread khác ra tín hiệu cho thread đang đợi để tiếp tục thi thi.
  • Timed Waiting: Một thread runnable có thể rơi vào trạng thái timed waiting trong một khoảng thời gian nhất định. Một thread trong trạng thái này sẽ trở về trạng thái runnable khi khoảng thời gian hết hoặc khi sự kiện nó đang đợi xay ra.
  • Terminated (Dead): Một thread runnable rơi vào trạng thái terminated khi nó hoàn thành tác vụ hoặc kết thúc.

2. Các mức ưu tiên Thread

  • Mỗi một thread Java có một mức độ ưu tiên giúp hệ điều hành xác định trình tự mà thread đã được lên lịch trình.
  • Độ ưu tiên Thread trong Java là phạm vi giữa MIN_PRIORITY (hằng số 1) và MAX_PRIORITY (hằng số 10). Theo mặc định, mỗi thread sẽ có mức độ ưu tiên là NORM_PRIORITY (hằng số 5).
  • Những thread có độ ưu tiên cao sẽ quan trọng hơn với một chương trình và nên được cấp phát thời gian bộ xử lý trước các thread có độ ưu tiên thấp hơn. Tuy nhiên, các mức ưu tiên của thread bảo đảm thứ tự trong đó các thread thực thi và phụ thuộc rất nhiều vào platform.

3. Tạo một Thread qua cài đặt một Runnable interface

Nếu muốn class của bạn thực thi như một thread bạn có thể cho class đó implements Runnable interface và lưu ý 3 bước cơ bản dưới đây: Step 1: Bạn cần implement phương thức run() được cung cấp bởi Runnable interface. Phương thức này cung cấp một điểm đầu vào cho thread và bạn sẽ đặt logi hoàn thiện vào phương thức này. Dưới đây là cú pháp đơn giản của phương thức run():

public void run( )

Step 2: Khởi tạo một đối tượng Thread, sử dụng contructor sau:

Thread(Runnable threadObj, String threadName);

Ở đây, threadObj là một instance của một lớp mà implement Runnable Interface và threadName là tên cho thread mới. Step 3: Khi một đối tượng Thread đã được tạo, bạn có thể bắt đầu nó bằng việt gọi tới phương thức start(), nó sẽ thực thi gọi phương thức run(), dưới đây là cú pháp đơn giản của phương thức start()

void start();

Ví dụ: Dưới đây là một ví dụ tạo mới một thread và bắt đầu chạy nó:

class RunnableDemo implements Runnable {
   private Thread t;
   private String threadName;
   
   RunnableDemo( String name) {
      threadName = name;
      System.out.println("Creating " +  threadName );
   }
   
   public void run() {
      System.out.println("Running " +  threadName );
      try {
         for(int i = 4; i > 0; i--) {
            System.out.println("Thread: " + threadName + ", " + i);
            // Let the thread sleep for a while.
            Thread.sleep(50);
         }
      }catch (InterruptedException e) {
         System.out.println("Thread " +  threadName + " interrupted.");
      }
      System.out.println("Thread " +  threadName + " exiting.");
   }
   
   public void start () {
      System.out.println("Starting " +  threadName );
      if (t == null) {
         t = new Thread (this, threadName);
         t.start ();
      }
   }
}

public class TestThread {

   public static void main(String args[]) {
      RunnableDemo R1 = new RunnableDemo( "Thread-1");
      R1.start();
      
      RunnableDemo R2 = new RunnableDemo( "Thread-2");
      R2.start();
   }   
}

Kết quả khi run chương trình trên như sau:

Creating Thread-1
Starting Thread-1
Creating Thread-2
Starting Thread-2
Running Thread-1
Thread: Thread-1, 4
Running Thread-2
Thread: Thread-2, 4
Thread: Thread-1, 3
Thread: Thread-2, 3
Thread: Thread-1, 2
Thread: Thread-2, 2
Thread: Thread-1, 1
Thread: Thread-2, 1
Thread Thread-1 exiting.
Thread Thread-2 exiting.

4. Tạo một Thread qua kế thừa một Thread class

Cách thứ 2 để tạo một thread là tạo mới một class kết thừa (extends) lớp Thread theo 2 bước đơn giản sau: Step 1: override phương thức run() có sẵn trong lớp Thread. Phương thức này cung cấp một điểm đầu vào cho thread và bạn sẽ đặt vào nó logic hoàn thành của bạn. Dưới đây là cú pháp đơn giản của phương thức run():

public void run( )

Step 2: Khi một đối tượng Thread đã được tạo, bạn có thể bắt đầu nó bằng việc gọi phương thức start(), nó sẽ thực thi gọi phương thức run(), dưới đây là cú pháp đơn giản của phương thức start():

void start();

Ví dụ: dưới đây là chương trình ở trên được viết lại theo cách kết thừa lớp Thread:

class ThreadDemo extends Thread {
   private Thread t;
   private String threadName;
   
   ThreadDemo( String name) {
      threadName = name;
      System.out.println("Creating " +  threadName );
   }
   
   public void run() {
      System.out.println("Running " +  threadName );
      try {
         for(int i = 4; i > 0; i--) {
            System.out.println("Thread: " + threadName + ", " + i);
            // Let the thread sleep for a while.
            Thread.sleep(50);
         }
      }catch (InterruptedException e) {
         System.out.println("Thread " +  threadName + " interrupted.");
      }
      System.out.println("Thread " +  threadName + " exiting.");
   }
   
   public void start () {
      System.out.println("Starting " +  threadName );
      if (t == null) {
         t = new Thread (this, threadName);
         t.start ();
      }
   }
}

public class TestThread {

   public static void main(String args[]) {
      ThreadDemo T1 = new ThreadDemo( "Thread-1");
      T1.start();
      
      ThreadDemo T2 = new ThreadDemo( "Thread-2");
      T2.start();
   }   
}

Kết quả khi run chương trình trên:

Creating Thread-1
Starting Thread-1
Creating Thread-2
Starting Thread-2
Running Thread-1
Thread: Thread-1, 4
Running Thread-2
Thread: Thread-2, 4
Thread: Thread-1, 3
Thread: Thread-2, 3
Thread: Thread-1, 2
Thread: Thread-2, 2
Thread: Thread-1, 1
Thread: Thread-2, 1
Thread Thread-1 exiting.
Thread Thread-2 exiting.

Vậy khi nào nên tạo thread bằng cách implements Runnable interface, và khi nào nên tạo thread bằng cách extends Thread class?

Chúng ta cùng phân tích lợi ích khi implements interface Runnable:

  • Nếu chúng ta implements interface Runnable, chúng ta vẫn có thể extends 1 class khác, nếu chúng ta extends Thread class chúng ta sẽ không thể extends thêm bất kì class nào vì Java không hỗ trợ đa kế thừa
  • Khi một class implements Runnable, ta nói class có khả năng “Runnable” hay còn gọi là mối quan hệ has – a, class extends Thread ta gọi class đó là “Thread” hay còn gọi mối quan hệ is – a. Trong thiết kế hướng đối tượng, thường dùng kế thừa has – a vì giúp giảm sự phụ thuộc code, code sẽ dễ test và maintain hơn.
  • Extends Thread có nghĩa là class chúng ta sẽ được kế thừa tất cả các method của Thread class, đôi khi không cần thiết, gây lãng phí bộ nhớ khi tạo thread.

Vậy khi nào ta tạo thread bằng cách extends Thread class?

Chỉ khi nào ta cần override và sử dụng các method của Thread class. Các trường hợp chỉ cần tạo thread để chạy, ta nên implements Runnable.

Part 2 cũng là phần cuối kết thúc chuỗi bài về kiến thức java core chuẩn bị cho automation test, để hiểu sâu hơn về các mảng kiến thức trong java mà mình đã nhắc đến hoặc chưa mọi người tìm hiểu thêm ở các nguồn tài liệu khác, mình chỉ dừng lại ở việc tóm tắt khái quát nhất và đem đến nhiều thông tin nhất những phần cơ bản về java core phục vụ cho việc test automation, ở bài viết sau, mình sẽ thực hiện viết những chương trình auto test đầu tiên.

Tài liệu tham khảo

  1. Translate: https://www.tutorialspoint.com
  2. Lập trình hướng đối tượng - PGS.TS. Trần Đình Quế

All rights reserved

Viblo
Hãy đăng ký một tài khoản Viblo để nhận được nhiều bài viết thú vị hơn.
Đăng kí