2019-01-24

Các câu hỏi hay gặp khi học OOP

Các câu hỏi hay gặp khi học OOP

Các câu hỏi hay gặp khi học OOP(Java)

Xin chào mọi người. Đây là bài viết đầu tiên của mình trên Kipalog nên sẽ có nhiều lỗi sai, mong mọi người chỉ giáo. Trong quá trình mình học về OOP trong Java thì cũng có những thắc mắc, mình xin phép được tổng hợp lại một số thắc mắc thường thấy khi bắt đầu nghiên cứu về OOP.

1. Abstract class khác Interface ở chỗ nào?

Đầu tiên thì phải định nghĩa lại hai khác niệm này đã:
  1. Abstract class: Đây là một Class cha dùng để định nghĩa về bản chất cho các Class con. Bản chất ở đây có thể hiểu là: loại, kiểu, nhiệm vụ, thuộc tính, ... của các Class. Hiểu nôm na là người con sẽ được kế thừa các đặc tình của người cha.
  2. Interface: Có thể hiểu đây là một dạng bảng thiết kế cho chức năng mà bất kỳ Class nào cũng có thể có. Một interface chứa các hành vi mà một class triển khai.
==> Điểm khác biệt đầu tiên có thể thấy là Abstract là một Class còn Interface thì không. Một Class có thể mô tả các thuộc tính và hành vi của một Object. Còn Interface lại chứa các hành vi mà một Class sẽ triển khai. Ngoài ra còn một số điểm khác biệt sau đây:
Abstract classInterface
Định nghĩa codeHỗ trợ định nghĩa cho các void, propertyKhông hỗ trợ định nghĩa mà chỉ cho khai báo
Về Access ModifiersCho phép xác định ModifierMặc định đều là public
Fields and ConstantsCó hỗ trợKhông hỗ trợ
Đa kế thừaKhông hỗ trợ, chỉ có thể extend 1 Abstract classCó hỗ trợ

2. Tại sao phải chia ra Abstract và Interface?

Như đã nói ở bên trên thì Interface có hỗ trợ đa kế thừa còn Abstract thì lại không. Ngoài ra thì còn một tác dụng nữa, bạn xem ví dụ sau nha:
Bài toán: Bạn cần phải tạo ra một vườn thú gồm có các con vật sau: Chó, Mèo, Chim và Cá. Mỗi loài vật đều có điểm chung như có tên, tiếng kêu nhưng cũng có điểm riêng như bơi, bay, chạy.
Vậy ta nên giải quyết vấn đề này như nào bây giờ? Chả nhẽ lại viết một Class Animal cha rồi ở mỗi Class con thì sẽ viết từng điểm riêng cho nó sao?
public abstract class Animal 
{
    public String name;
    public String sound;

    public Animal(String name, String sound) {
        this.name = name;
        this.sound = sound;
    }    
}

public class Cat extends Animal
{    
    private void iRun()
    {
        System.out.println(name + "run");
    }
    public Cat(String name, String sound) {
        super(name, sound);
    }    
}
...
Vậy thì dài quá :( Nhưng chúng ta hãy phân tích lại nhé :) Mỗi loài vật đều có những điểm chung cố định, vậy nên chúng ta sẽ cho nó vào 1 Abstract class. Còn các điểm riêng thì sao? Đây chính là lúc mà Interface phát huy tác dụng, nó dùng các chức năng mà :) Nên chúng ta có thể sử dụng chúng để thiết kế cho từng chức năng Chạy, Bơi, Bay, ... Rồi implements cho từng Class riêng biệt. Điều này cũng có thể giải quyết cho những loài vật có nhiều khả năng như cả chạy cả bay.
Chốt lại, bạn nên xác định rõ dùng Abstract hay Interface qua các điều kiện sau đây:
Abstract classInterface
  • Bạn muốn chia sẻ đoạn code trùng nhau với các class liên quan.
  • Bạn thấy các Class con có nhiều method , field giống nhau.
  • Bạn không cần sử dụng cơ chế multiple inheritance.
  • Các class không liên quan với nhau.
  • Bạn muốn tận dụng cơ thế multiple inheritance.

3. Tại sao nên khai báo <Lớp cha> = new <Lớp con>

Điều này được gọi là tính Đa Hình trong OOP.
Ví dụ ta có 1 Class Shape là class cha và 2 Class HinhTamGiac, HinhVuong là class con như sau:
public abstract class Shape 
{
    public abstract int tinhChuVi();
    public abstract void veHinh();
}
public class HinhTamGiac extends Shape
{
    @Override
    public int tinhChuVi() 
    {
        return 1;
        // Ở đây chỉ là ví dụ thôi
    }

    @Override
    public void veHinh() 
    {
        System.out.println("Tam Giac");
    }    
}
public class HinhVuong extends Shape
{
    @Override
    public int tinhChuVi() {
        return 2;
    }

    @Override
    public void veHinh() {
        System.out.println("Hinh Vuong");
    }    
}
Có phải nó rất dài không, tại sao lại phải viết thêm Class cha mà sao không viết hẳn luôn 2 class con ra ? :smile: Nhưng nếu bây giờ ta có một Class Controller nữa:
public class Controller 
{
    public static void main(String[] args) 
    {
        Shape[] list = new Shape[2];
        list[0] = new HinhTamGiac();
        list[1] = new HinhVuong();

        for(Shape shape : list)
        {
            printInfo(shape);
        }
    }
    public static void printInfo(Shape shape)
    {
        System.out.println("Chu vi: " + shape.tinhChuVi());
        shape.veHinh();
    }
}  
Nếu trong trường hợp này, bạn viết riêng 2 Class con ra thì tức là bạn sẽ phải viết từng void printInfo cho từng Class cụ thể. Đồng thời cùng phải tạo ra hai Array riêng để chứa từng loại ==> Quá dài dòng và phức tạp đúng không ?? Điều này rất bất tiện, mỗi khi có kiểu Shape mới bạn lại phải vào sửa, thêm phương thức trong Controller.java ==> Mất thời gian.
==> Chốt lại: Tính đa hình giúp cho các Class chỉ cần quan tâm đến đối tượng mình đang làm việc có thể làm được gì, chứ không quan tâm đó là cái gì :smile: Đây là một đặc điểm của OOP :wink:

4. Có thể khai báo <Lớp con> = new <Lớp cha> được không?

Câu trả lời là không được :) Vì khi khai báo <Lớp con> = new <Lớp cha> tức là bạn đang tạo một Object <Lớp con> và chứa nó trong <Lớp cha>. Lớp con sẽ nhiều thuộc tính/phương thức hơn so với lớp cha.Nếu khai báo dữ liệu kiểu con thì cần điền đủ dữ liệu cho nó, nhưng bạn lại new lớp cha, có dữ liệu ít (không đủ).
Vẫn có trường hợp lớp con chỉ kế thừa mà không thêm thắt gì cả. Nhưng để đảm bảo tính “mở rộng” thì <Lớp con> = new <Lớp cha> không được phép.

5. Upcasting và Downcasting là gì?

Đây là 2 cơ chế ép kiểu sử dụng phổ biến cho các loại biến tham chiếu.
1.Upcasting: Đây là cơ chế để bạn chuyển từ đối tượng Class con sang đối tượng là Class cha. Chú ý là lúc này đối tượng đó sẽ không dùng được các method của Class con nữa. Và nó cũng khác với việc khai báo <Lớp con> = new <Lớp cha>.
Cat cat = new Cat();
Animal animal2 = (Animal) cat; // Upcasting
2.Downcasting: Ngược lại thì đây là cơ chế để bạn chuyển từ đối tượng Class cha sang đối tượng là Class con. Chú ý là bạn chỉ có thể Downcasting các Object được khai báo kiểu <Lớp cha> = new <Lớp con> vì khi đó thì mới có thể chứa được Object mới. Cơ chế này thường được sử dụng khi bạn khai báo tính đa hình và muốn sử dụng các method, ... từ Class con.
Animal animal = new Cat();
Cat cat = (Cat) animal;

Cảm ơn các bạn đã đọc hết bài viết này :) Hy vọng các bạn ủng hộ và góp ý để mình viết các bài sau tốt hơn :)

2019-01-20

Tìm hiểu về domain name system

Tìm hiểu về domain name system

Tổng quan

DNS - Domain Name System hay hệ thống tên miền là một cơ sở dữ liệu phân tán nằm trên các server khác nhau lưu thông tin ánh xạ giữa domain name và địa chỉ IP. DNS thực hiện dịch domain name tới một địa chỉ IP và ngược lại.
Sau đây là sơ đồ hoạt động của DNS.

Một số khái niệm trong DNS

Domain name space

Là một cấu trúc dạng cây chứa toàn bộ không gian domain như hình ảnh sau đây:

Domain

Là một nhánh của domain name space. Sau đây ví dụ về domain "purdue.edu".

Domain name

Mỗi node trong cây có một nhãn. Một domain name của bất kỳ một node nào trong cây là thứ tự các nhãn trong đường dẫn từ node đó đến root, tên mỗi node được phân cách bằng dấu chấm .

Resource Record

Thông tin về domain name được lưu trong các resource record. Mỗi resource record đặc tả thông tin về một đối tượng cụ thể. DNS server sử dụng các record này để trả lời các DNS query. Dưới đây là một vài resource record phổ biến:
  • SOA - Start of authority: Record lưu thông tin quản trị zone và mỗi zone chỉ có duy nhất một SOA.
  • NS - Name server: Record lưu ánh xạ domain name tới một danh sách các name server.
  • A: Record lưu ánh xạ domain name tới một địa chỉ IP.
  • PTR - Reverse-lookup pointer: Record lưu ánh xạ địa chỉ IP tới một domain name.
  • MX - Mail exchange: Record lưu ánh xạ domain name tới một danh sách các mail server. Ví dụ khi bạn gửi mail tới contact@example.com thì mail sẽ được chuyển tới mail server được đặc tả trong MX Record.
  • CNAME: Record lưu ánh xạ một domain name tới một domain name khác.

Top level domain

Là domain ở cấp cao nhất ngay bên dưới root domain trong hệ thống phân cấp của DNS. Top level domain được chia ra làm 2 loại chính:
  1. Generic TLD (gTLD)
  2. Country Code TLD (ccTLD)
Ví dụ một vài gTLD:
  • .com: Cho các website thương mại
  • .org: Cho các tổ chức phi lợi nhuận
  • .edu: Giới hạn trong các trường học và tổ chức
  • .net: Ban đầu giới hạn trong các tổ chức hạ tầng mạng nhưng bây giờ thì không hạn chế
Ví dụ một vài ccTLD:
  • .us: United States
  • .in: India
  • .uk: United Kingdom
  • .vn: Vietnam
Hình ảnh cấu trúc cây DNS:

Delegation và Authority

Một trong những mục tiêu chính của thiết kế hệ thống DNS là phân cấp quản trị. Nó đạt được thông qua delegation - uỷ thác.
Cơ quan quản lý domain gọi là Authority. Cơ quan quản lý root domain và gTLD thuộc về ICANN - Internet Corporation for Assigned Names and Numbers. Tên miền ccTLD thì được quản lý bởi các quốc gia.
Một tổ chức quản lý domain có thể chia nó thành các sub-domain. Mỗi sub-domain có thể ủy quyền quản trị cho các tổ chức khác. Ví dụ domain "standford.edu" được uỷ quyền quản trị tới trường đại học Standard.

Name server và Zone

Name server: Là server lưu trữ các resource record về domain và thông tin name server của các sub-domain của nó. Các name server thường chỉ chứa thông tin đầy đủ về một phần của domain name space được gọi là zone.
Lưu ý: Root name server là name server của root domain hay name server gốc của toàn bộ hệ thống DNS.
Sau đây là ví dụ domain edu được chia thành các zone như: zone berkeley.edu, zone purdue.edu và zone nwu.edu zone. Và bản thân edu cũng là một zone.

DNS Resolver

Phía client của DNS gọi là DNS Resolver. DNS Resolver chịu trách nhiệm thực hiện các DNS query.

DNS Query

Có 3 loại DNS query:
  • Recursive query: Truy vấn đệ quy
  • Iterative query: Truy vấn lặp đi lặp lại
  • Inverse query: Truy vấn domain name cho địa chỉ IP chỉ định



Sau đây là các bước khi thực hiện phân giải tên miền chẳng hạn rutgers.edu.
  • Resolver sẽ gửi một query tới DNS local
  • DNS local sẽ tìm kiếm rutgers.edu trong cơ sở liệu của nó. Nếu có trả về địa chỉ IP cho Resolver. Nếu không thực hiện bước tiếp theo.
  • Resolver sẽ gửi query tới root name server
  • Root name server sẽ uỷ thác trả lời query đến name server của domain .edu
  • Name server của domain .edu tiếp tục uỷ thác trả lời query đến name server của domain rutgers.edu
  • Trả về thông tin IP được lấy từ A record cho Resolver.

Một vài mẹo khi sử dụng DNS

Cấu hình DNS local sử dụng hosts file

Tôi gặp một tình huống như sau: Khách hàng sẽ migration server sang một server mới mà không stop service trên server cũ + một số rule sercurity mà khách hàng config 2 server vs thông tin kiểu như sau:
  • Domain name example.com được gắn với IP của server A.
  • Cả server A và server B đều có cùng config server_name = example.com trên web server.
Vậy làm thế nào để access vào website example.com của server B?
Tôi thêm config sau vào file hosts với path là /etc/hosts ở các máy hệ linux hay mac.
SERVER-B_IP_ADDRESS example.com
Lưu ý: Host file được sử dụng giống như một resource record để phân giải domain name tới địa chỉ IP.
Khi đó nếu bạn gõ example.com lên browser nó sẽ chạy đến website example.com của server B thay vì server A. Để xác nhận xem config đã hoạt động ổn chưa bạn bật Develop Tools của browser rồi kiểm tra thông tin Headers của request có dạng như sau:
Request URL: http://example.com/
Request Method: GET
Status Code: 200 OK
Remote Address: SERVER_ADDRESS
Referrer Policy: no-referrer-when-downgrade

Cấu hình DNS cho domain name

Để một website hoạt động thì ta phải thực hiện thao tác trỏ domain name về website đó. Và có 2 cách để làm việc này đó là:
  • Trỏ thẳng domain name tới địa chỉ IP của server chứa website đó.
    hoặc
  • Thêm thông tin name server của nhà cung cấp server vào domain name.
Sau khi config xong bạn có thể sử dụng tool sau để kiểm tra thông tin cấu hình domain name:
https://ipinfo.info/html/ip_checker.php/

Sử dụng CNAME record

Giả sử bạn có 2 website là example.com và example.vn trên cùng một server. Khi đó bạn có thể config 2 domain name trỏ đến cùng một địa chỉ IP nhưng nếu bạn có ý định đổi địa chỉ IP của server thì bạn phải config IP lại cho cả 2 domain name trên.
Tuy nhiên nếu sử dụng CNAME record chẳng hạn trỏ domain name example.vn tới domain name example.com như ví dụ bên dưới thì khi đó bạn cần config IP lại chỉ ở domain main example.com mà thôi.
NAME               TYPE    VALUE
---------------------------------------
example.vn.        CNAME   example.com.
example.com.        A      192.0.2.23

Tài liệu tham khảo

Làm sao để convert String sang Number trong JavaScript?

Làm sao để convert String sang Number trong JavaScript?

JavaScript cung cấp nhiều cách khác nhau để convert String sang Number. Và trong bài viết này, mình sẽ tổng hợp lại một số cách mà mình đã biết.

Sử dụng Number() function

Đúng vậy, đó là Number() function chứ không phải Number() constructor.
Vì Number() constructor (là khi bạn sử dụng với từ khoá new) sẽ tạo mới một Number Object với rất nhiều thứ kèm theo. Còn Number() function sẽ tạo ra một số thông thường (primitive).
const x = new Number("6666");
console.log(x); // => Number {6666}

const y = Number("6666");
console.log(y); // => 6666
Một số ví dụ khác:
const x = Number('66,66');
console.log(x); // => NaN

const y = Number('66.66');
console.log(y); // => 66.66

const z = Number('66666');
console.log(z); // => 66666
Qua ví dụ trên, bạn thấy rằng: nếu String truyền vào không phải Number thì kết quả thu được là NaN; nếu tham số truyền vào không phải số nguyên thì kết quả không bị làm tròn.

Sử dụng parseInt() và parseFloat()

Phương thức parseInt()

Phương thức parseInt() là một giải pháp thường hay được sử dụng. Phương thức này sẽ parse String tuỳ theo hệ cơ số xác định. Vì vậy, mình đã thường xuyên sử dụng cách này để convert String sang Number trong JavaScript.
Ví dụ:
const x = parseInt('1000.12', 10);
console.log(x); // => 1000

const y = parseInt('1000', 16);
console.log(y); // => 4096

const z = parseInt('1000', 8);
console.log(z); // => 512

const t = parseInt('1000', 2);
console.log(t); // => 8 
Với giá trị của hệ cơ số hợp lệ là từ 2 đến 36. Trong trường hợp bạn không truyền giá trị của hệ cơ số vào thì JavaScript sẽ tự parse theo nguyên tắc:
  • String bắt đầu bằng "0x" hoặc "0X" => hệ cơ số 16 (hexadecimal)
  • String bắt đầu bằng "0" => hệ cơ số 8 (octal) hoặc 10 (decimal) tuỳ thuộc vào trình duyệt
  • String bắt đầu bằng các số khác thì => hệ cơ số 10 (decimal)
  • String bắt đầu không phải là số => NaN
Ví dụ:
const x = parseInt('1000.12');
console.log(x); // => 1000

const y = parseInt('0x1000');
console.log(y); // => 4096

const z = parseInt('01000');
console.log(z); // => 1000

const t = parseInt('b1000');
console.log(t); // => NaN
Kết quả có vẻ hơi khó đoán nhỉ?
Theo mình, tốt nhất là luôn truyền vào giá trị của hệ cơ số muốn parse để luôn thu được giá trị như mong muốn.

Phương thức parseFloat()

Nếu như phương thức parseInt() luôn trả về kết quả là một số nguyên thì phương thức parseFloat() sẽ trả về kết quả là một số float.
Ví dụ:
const x = parseFloat('1000,12', 10);
console.log(x); // => 1000

const y = parseFloat('1000.12', 10);
console.log(y); // => 1000.12

const z = parseFloat('1000.120', 10);
console.log(z); // => 1000.12

const t = parseFloat('1000', 10);
console.log(t); // => 1000

Sử dụng toán tử +

Ví dụ:
const x = +'1000,12';
console.log(x); // => NaN

const y = +'1000.12';
console.log(y); // => 1000.12

const z = +'1000.120';
console.log(z); // => 1000.12

const t = +'1000';
console.log(t); // => 1000

Sử dụng Math.floor()

Phương thức Math.floor() khá giống với phương thức parseInt() phía trên.
Ví dụ:
const x = Math.floor('1000.12');
console.log(x); // => 1000

const y = Math.floor('0x1000');
console.log(y); // => 4096

const z = Math.floor('01000');
console.log(z); // => 1000

const t = Math.floor('b1000');
console.log(t); // => NaN

Sử dụng toán tử *

Ví dụ:
const x = '1000,12' * 1;
console.log(x); // => NaN

const y = '1000.12' * 1;
console.log(y); // => 1000.12

const z = '1000.120' * 1;
console.log(z); // => 1000.12

const t = '1000' * 1;
console.log(t); // => 1000

Sử dụng toán tử /

Ví dụ:
const x = '1000,12' / 1;
console.log(x); // => NaN

const y = '1000.12' / 1;
console.log(y); // => 1000.12

const z = '1000.120' / 1;
console.log(z); // => 1000.12

const t = '1000' / 1;
console.log(t); // => 1000

Sử dụng toán tử -

Ví dụ:
const x = '1000,12' - 0;
console.log(x); // => NaN

const y = '1000.12' - 0;
console.log(y); // => 1000.12

const z = '1000.120' - 0;
console.log(z); // => 1000.12

const t = '1000' - 0;
console.log(t); // => 1000

Sử dụng Bitwise not ~~

Ví dụ:
const x = ~~'1000,12';
console.log(x); // => 0

const y = ~~'1000.12';
console.log(y); // => 1000

const z = ~~'1000.120';
console.log(z); // => 1000

const t = ~~'1000';
console.log(t); // => 1000

Sử dụng toán tử dịch >>>

Ví dụ:
const x = '1000,12' >>> 0;
console.log(x); // => 0

const y = '1000.12' >>> 0;
console.log(y); // => 1000

const z = '1000.120' >>> 0;
console.log(z); // => 1000

const t = '1000' >>> 0;
console.log(t); // => 1000

Lời kết

Trên đây là một số cách để convert String sang Number trong JavaScript. Để so sánh tốc độ giữa các cách này, bạn có thể tham khảo và viết thêm testcase để so sánh tại Convert a string to a number using JavaScript.

Ngoài ra, bạn thường hay sử dụng cách nào để convert String sang Number trong JavaScript? Mình hy vọng được biết thêm nhiều cách khác nữa của bạn trong phần bình luận phía dưới!
Xin chào và hẹn gặp lại, thân ái!