ลองมาอธิบายความแตกต่าง Abstraction, Encapsulation, Cohesion
#1
Posted 27 February 2004 - 02:25 AM
์ทั้งสามตัวนี้อย่างไร ลองอธิบายตามความเข้าใจตัวเองดู ผิดถูกไม่เป็นไรแต่อยากจะให้ลองฝึกทักษะการอธิบายว่าเราเข้าใจคำว่า Abstraction, Encapsulation, Cohesion อย่างไรและหลักการเหล่านี้สำคัญอย่างไรครับ
#2
Posted 27 February 2004 - 01:24 PM
Abstraction -> ผมคิดว่าเป็นการกำหนดรูปแบบต่างๆไว้เป็นมาตรฐานก่อนที่เราจะนำไปใช้ เมื่อเรานำไปใช้ก็ค่อยนิยามความหมายของมันลงไป เหมือนกับการสร้าง interface
Cohesion -> ความสัมพันธ์ภายใน class ที่เคยได้ยินมาคือเราต้องพยายามออกแบบโปรแกรมให้สอดคล้องกับคำกล่าวว่า Strongly cohesion , looselu coupling ซึงก็คือ พยายามเขียนโปแกรมให้สัมพันธ์กันภายในคลาสให้มากที่สุด แพยายามลดความสัมพันธ์ระหว่าง class ให้น้อยที่สุด
Encapsulation -> จากที่เคยอ่านมา เป็นการนำส่วนที่เป็นข้อมูล(data) กับ operaton(method) มาผูกติดกัน เพื่อที่จะป้องกันการเข้าถึงข้อมูลได้โดยตรงโดยการใช้คีย์เวิร์ด private ส่วน method ก็ได้แก่ Accesors เช่น getName(), getSize(), .... Mutators เช่น setName(), setSize()...
#3
Posted 27 February 2004 - 05:42 PM
abstraction เป็นแนวทางหรือ framework ที่เรากำหนดขึ้นเองในโปรแกรมของเรา โดยมีจุดประสงค์
ให้เป็นการบังคับให้ class ทั้งหมดที่อยู่ในตระกูลเดียวกันใช้ method ที่มีชื่อเดียวกันแต่มี function ต่างกันหรือ abstract method
เช่น class คนทำอาหาร กับ class พนักงานเสริฟ ต่างอยู่ในตระกูล "พนักงาน" เหมือนกัน
หากเราสั่งให้คนทำอาหารและพนักงานเสริฟให้ทำงาน แต่ละคนนั้นจะมีหน้าที่ไม่เหมือนกัน แต่ใช้ method ชื่อเดียวกัน
ซึ่งจริงๆแล้วในการเขียนโปรแกรมเราไม่จำเป็นต้องทำ abstract class ก็ได้แต่การที่เราได้แยกแยะ class ต่างๆอย่างเป็นสัดส่วน
จะทำให้เรา maintain แก้ไขโปรมแกรมของเราง่ายและเข้าใจภาพรวมง่าย และทำให้เกิดผลพลอยได้ด้าน reuseability ด้วย
การทำ abstraction จะทำให้โครงสร้าง class ต่างๆมีความเป็น module มาก ซึ่งเรียกได้ว่ามีความเป็น modularity สูง
ซึ่งโปรแกรมที่มีความเป็น modularity สูงก็จะมีคุณสมบัติ cohesive และ loosely coupled ด้วยโดยปริยาย
คุณสมบัติในการเป็น module คือจะเปิดช่องให้ objects อื่นได้เข้าถึงข้อมูลผ่าน interface หรือ methods ที่เป็น API
และจะซ่อนสิ่งที่เหลือไว้หรือ encapsulate ตัวเองนั่นเอง
ผมว่าทฤษฎี object oriented มีความเกี่ยวโยงกันโดยเมื่อนึกถึง abstraction ก็จะนึกถึง Interface ไปด้วย
ซึ่งเราอาจึกถึงกรณีที่ พนักงาน ทั้ง คนทำอาหาร และพนักงานเสริฟ ต่างก็มีสามารถเก็บโตะ ซึ่งแยกเป็น Interfafce ได้เหมือนกัน
แต่ก็อีก่นแหละครับ เราไม่จำเป็นต้องสร้าง Interface เพราะเป็น framework เฉยๆ แต่หากทำงานบ่อยๆก็จะรู้ปัญหามากขึ้น
และอยากจะทำ interface ไปเองแบบไม่ต้องฝืนครับ
#4
Posted 28 February 2004 - 12:45 AM
Abstraction - หมายความว่านามธรรม ... คือมีแต่ "นาม" (ชื่อ) แต่ไม่ใช่รูปธรรม (ตัวตน) ผมเข้าใจว่า Abstract Class เป็นเพียงคำอธิบาย Class ที่มี Abstract Method เท่านั้น เพราะคำว่านามธรรม ควรจะเอามาอธิบาย Method มากกว่า ... จากที่บอกว่า มันคือ "ชื่อ" ไม่ใช่ "ตัวตน" เราเลยต้องสร้างตัวตนให้มันโดยการ implement method นั้น ๆ ... จบ
Cohesion - เพิ่งเคยได้ยินง่ะ
Encapsulation - เอาข้อมูลไปใส่ในแคปซูล (เอาไปห่อหุ้ม) ... คงหมายถึง การป้องกันการเข้าถึงข้อมูลโดยตรง เพื่อความปลอดภัยบางอย่าง ในทาง coding ก็ทำการใส่ access control modifier (public, protected, {default}, private) หน้าคำประกาศ class member คงเป็นเพราะ ข้อมูลบางอย่างไม่สมควรได้รับการเข้าถึงโดยตรง เนื่องจากอาจจะทำให้ Class ทำงานผิดพลาดได้
#5
Posted 28 February 2004 - 10:53 PM
รับ
Quote
Quote
าสมีความเกี่ยวข้องกันให้น้อย จะนำไปสู่ระบบที่มีความยืดหยุ่น มีความเป็นไปได้ที่จะ reuse คลาสแต่ละตัวได้ง่าย เนื่องจากคลาสแต่ละตัวสามารถทำงานตามจุดประสงค์ตัวเองได้ครบและไม่ขึ้นอยู่การทำงานข
องคลาสตัวอื่น ในทางปฏิบัติคลาสปกติจะติดต่อกับคลาสอื่นเสมอแต่อย่าผูกติดกับคลาสอื่นมากๆ เช่น พยายามเลี่ยงการใช้ friend ใน C++ หรือพยายามใช้ observer pattern ในการเขียนโปรแกรมด้วยหลัก MVC
Quote
#6
Posted 28 February 2004 - 11:34 PM
Quote
ให้เป็นการบังคับให้ class ทั้งหมดที่อยู่ในตระกูลเดียวกันใช้ method ที่มีชื่อเดียวกันแต่มี function ต่างกันหรือ abstract method
เช่น class คนทำอาหาร กับ class พนักงานเสริฟ ต่างอยู่ในตระกูล "พนักงาน" เหมือนกัน
หากเราสั่งให้คนทำอาหารและพนักงานเสริฟให้ทำงาน แต่ละคนนั้นจะมีหน้าที่ไม่เหมือนกัน แต่ใช้ method ชื่อเดียวกัน
...
การทำ abstraction จะทำให้โครงสร้าง class ต่างๆมีความเป็น module มาก ซึ่งเรียกได้ว่ามีความเป็น modularity สูง
ผมคิดว่าคุณ abcdefg อธิบายในเชิงการทำ inheritance เพื่อสร้าง class hierarchy และการทำ abstract class/method มากกว่า แต่ผมเห็นด้วยถึงประโยชน์ของการสร้าง abstract method ในจาวาหรือกำหนด virtual function ใน C++ ว่าเป็นการ reuse interface (method signature) เพื่อให้ subclass กำหนดพฤติกรรมตาม interface ที่กำหนดจาก superclass นั้น แต่ไม่ได้ช่วยในแง่ implementation reuse เพราะ abstract method ไม่มี method body ให้ใช้ ส่วนการนิยาม abstract class โดยมีเมดธอดที่ไม่ได้เป็น abstract และ final ที่มี method body อันนี้จะสามารถถูก reuse ให้ subclass นำไปใช้ได้โดยตรง หรือนำไป extend ด้วยการนิยามเมดธอดชื่อเดียวกันแล้วเปลี่ยนแปลง/เพิ่มเติมพฤติกรรมได้
Quote
จะทำให้เรา maintain แก้ไขโปรมแกรมของเราง่ายและเข้าใจภาพรวมง่าย และทำให้เกิดผลพลอยได้ด้าน reuseability ด้วย
Quote
นิยามของโปรแกรมที่มีความเป็น module สูงเป็นอย่างไรครับ ถ้าผมมีโมดูลระบบ billing แต่จะรันได้ก็ต่อเมื่อมีโมดูล inventory, customer, reporting, sales, accounting ด้วยจะถือว่าโมดูลนี้ loosely coupled หรือเปล่าครับ
Quote
และจะซ่อนสิ่งที่เหลือไว้หรือ encapsulate ตัวเองนั่นเอง
Quote
#7
Posted 29 February 2004 - 01:54 AM
Quote
จริงครับ ผมไม่ได้สังเกตุตัวเองเลยว่าคิดแบบนี้
เพราะผมมักคิดว่า class ที่อยู่บนสุด มีโอกาสที่จะเป็น abstract ได้มากครับ
ไม่รู้อะไรทำให้ฝังใจแบบนี้เหมือนกันครับ
#8
Posted 29 February 2004 - 05:16 AM
Quote
ตอบได้ดีมากครับ ตามหนังสือ OOP เค้าอธิบายว่า Abstraction คือกระบวนการวางหลักการโดยละไว้ถึงรายละเอียดปลีกย่อย (suppression to details) เช่น เราต้องการจะพัฒนาโปรแกรมธุรกิจ เราก็จะศึกษาและแทนระบบธุรกิจ(ที่ไม่เกี่ยวอะไรกับคอมพิวเตอร์)ด้วยโปรแกรมคอมพิวเตอ
ร์ นั่นคือเราต้องการ abstract business system into software representation
การกระทำดังกล่าวเราไม่จำเป็นต้องโมเดลระบบซอฟท์แวร์ให้ครอบคลุมถึงทุกกิจกรรมของธุร
กิจ แต่เราจะโมเดลเฉพาะธุรกรรมที่เราให้ความสนใจ เช่น การทำระบบจัดซื้อ ระบบบัญชี ระบบเงินเดือน ระบบการผลิต ระบบลูกค้าสัมพันธ์ ฯลฯ โดยไม่สนใจเรื่องระบบไฟฟ้า ระบบอำนวยความสะดวกในออฟฟิศ (ยกเว้นเราอาจต้องการตัวเลขเกี่ยวกับค่าใช้จ่ายในการดำเนินกิจกรรมนั้นๆ) นั่นคือเราไม่จำเป็นต้องรวมระบบเหล่านี้เข้ากับระบบซอฟท์แวร์ที่เราออกแบบ
หลังจากที่เราแบ่งระบบเป็นหน่วยย่อยๆแล้ว เราจึงจะมาออกแบบรายละเอียดของแต่ละระบบย่อย หลักการ abstraction ก็สามารถนำมาใช้ได้เช่นกัน เราจะลงลึกเข้าไปในรายละเอียดมากขึ้น แต่ยังละส่วนที่เป็นรายละเอียดที่ยังไม่เกี่ยวข้องโดยตรง โดยมองว่าเราจะลงลึกไปถึงรายละเอียดเหล่านั้นเมื่อเราต้องการออกแบบรายละเอียดถึงจุด
นั้น บางคนจะมองการทำ abstraction ในแง่ของการทำ module โดยแบ่งเป็นชั้นๆ (layering) ซึ่งการจัดแบ่งปัญหาและการแก้ปัญหาเป็นระดับๆ คือวัตถุประสงค์ในการออกแบบที่ใช้การแบ่งระดับของ abstraction (level of abstraction)
ยกอย่างเช่น เราต้องการฐานข้อมูลลูกค้า ในระดับบนเราไม่สนใจว่าฐานข้อมูลนี้จะถูกเก็บอย่างไร เราต้องการแค่ว่าถ้าเราค้นข้อมูลลูกค้าจากหมายเลขบัญชีลูกค้า เราก็จะได้ข้อมูลของลูกค้านั้นๆ พอเรามาออกแบบฐานข้อมูลในระดับถัดไป เราอาจจะต้องเริ่มมองว่าเราจะเก็บฐานข้อมูลโดยผ่าน persistence framework หรือใช้ฐานข้อมูลสัมพันธ์ (relational database) หรือฐานข้อมูลเชิงวัตถุหรือเก็บเป็นฐานข้อมูลโครงสร้างแบบ XML ซึ่งแต่ละทางเลือกก็จะมีวิธีการออกแบบในระดับถัดไปที่แตกต่างกันเป็นต้น
การทำ abstraction และการแบ่งเป็นระดับๆ (level of abstraction) เป็นหลักการที่ทำให้เราสามารถแบ่งปัญหาได้เป็นชั้นๆเพื่อลดความซับซ้อนในการออกแบบโด
ยไม่ต้องคำนึงถึงปัญหาทุกอย่างพร้อมๆกันทีเดียว การลดความซับซ้อน (complexity) ถือว่าเป็นกุญแจในการออกแบบระบบซอฟท์แวร์ที่สำคัญอย่างหนึ่งเลยทีเดียว
Quote
Cohesion ผมไม่แน่ใจว่าภาษาไทยใช้คำว่าอะไร แต่เป็นหลักการในการออกแบบ module (สำหรับระบบ procedural) หรือคลาส (สำหรับระบบเชิงวัตถุ) ที่มุ่งเน้นจุดที่ว่ากลุ่มข้อมูลและการจัดการกับข้อมูลเหล่านั้นควรมีความสอดคล้องกั
น คลาสหนึ่งคลาสควรมีหน้าที่ที่ชัดเจน และไม่ควรทำอะไรนอกเหนือจากหน้าที่นั้น คลาสที่เป็น Utility class ที่มักจะทำอะไรได้หลายๆอย่าง มักจะมี cohesion ต่ำซึ่งจริงๆแล้วถือว่าไม่ดี แต่บางครั้งก็เลี่ยงไม่ได้เหมือนกันครับ แต่โดยทั่วๆไปแล้วคลาสแต่ละคลาสควรออกแบบให้มี strong cohesion
Coupling คือการบ่งบอกถึงการขึ้นต่อของคลาสหนึ่งกับคลาสอื่นๆ ถ้าคลาสนี้เรียกใช้ได้ก็ต่อเมื่อต้องมีคลาสอื่นอีกมากน้อยเพียงใด การที่คลาสหนึ่งขึ้นอยู่กับคลาสอื่นๆมาก ก็จะทำให้การแก้ไขระบบไม่ยืดหยุ่น เพราะเราจะไม่แน่ใจว่าถ้าเราแก้คลาสนี้แล้วจะมีผลกระทบต่อคลาสอื่นๆอย่างไร (เช่น แก้ค่าคงที่ในคลาสนี้ในขณะที่มีคลาสอื่นหลายสิบตัวใช้ค่าคงที่นี้ หรือแก้เมดธอดหนึ่งที่มีคลาสจำนวนมากเรียกใช้ เป็นต้น) หนึ่งในวิธีการลดการขึ้นต่อคลาสอื่น (loosely coupled) สามารถทำได้โดยการใช้ observer pattern ครับ
Quote
ถูกต้องแล้วครับ Encapsulation จะทำกับข้อมูล (data) โดยการสร้างเมดธอดที่ใช้ในการอ่าน/เขียนข้อมูลเพื่อป้องกันไม่ให้ใครมาแก้ไขข้อมูลนั้นโดยตรง แต่คำถามคือแล้วมันต่างกันอย่างไรครับ เพราะถ้าเราสามารถแก้ข้อมูลผ่านเมดธอดได้อยู่แล้ว ทำไมไม่อนุญาติให้เราเข้าไปทำกับข้อมูลโดยตรงล่ะครับ
#9
Posted 29 February 2004 - 11:28 AM
(ทุกวันนี้ใช้แต่ abstract -_-")
#10
Posted 29 February 2004 - 03:43 PM
Quote
ผมคิดว่า (เดาอีกแล้ว) ... บางครั้งสิ่งที่เห็น ก็ไม่ใช่สิ่งที่เป็น (What You See Is Not What You Get) ... ผมว่า encapsulation มีประโยชน์หลายอย่างครับ
อย่างนึงคงเป็นการปิดบังโครงสร้างข้อมูลที่แท้จริงเอาไว้ เช่น
ผมมีตัวแปรตัวนึงที่เก็บค่าเป็น URL สมมติว่าชื่อ x แต่ผมสร้าง setter method ให้มันสองตัว เพื่อความสะดวก คือ setX( String path ) กับ setX( URL u ) ... อาจจะมีวิธีอื่นด้วย เช่น setX( String protocol, String path ) สรรเพเหระที่จะนึกออก ... programmer ไม่จำเป็นต้องรู้ว่า ผมใช่ Class ไหนในการเก็บข้อมูล ... ส่ง ๆ มาเหอะ บริการให้ได้ก็แล้วกัน
ดีไม่ดี ผมอาจจะเก็บเป็น String host, int port, String path ก็ได้เอาให้ทำงานได้ง่ายก็แล้วกัน
แล้วเท่าที่ดูจาก Java SourceCode (src.zip) ผมเห็นการใช้งาน encapsulation ของคลาสต่าง ๆ น่าสนใจทีเดียว ... เขาเอา Encapsulation ไปใช้ เพื่อจัดการการเข้าถึงในหัวข้อของ method chaining กับ synchronize
Synchronized
เพราะว่า ... หากปล่อยให้โปรแกรมเมอร์แก้ค่ากันดื้อ ๆ อาจจะทำให้ Thread อื่นที่ใช้งานตัวแปรนั้นอยู่พังเอาดื้อ ๆ เช่นเดียวกัน ... setter หลาย ๆ ตัวที่เห็น จึงมีการใช้ synchronized block เพื่อป้องกันปัญหานี้ ...
/**
* Safely set this variable.
*/
public void setX( URL u ) {
synchronized( this.u ) {
this.u = u;
}
}
บางที เราไม่ synchronize ที่ object นั้นโดยตรง แต่ไป synchronize ที่ class ที่บรรจุมันอยู่
public synchronized void setX( URL u ) {
this.u = u;
}
Method chaining
ส่วนใหญ่ที่เจอ เขาเอาไว้จัดการกับ deprecated method ครับ เช่น
setDefaultProperty( String key, String value ) ถูกประกาศ deprecated แล้ว ให้ไปใช้ setProperty( String key, String value ) แทน ซึ่ง Java API ยังต้องรักษา method นี้เอาไว้เพื่อให้คลาสเก่า ๆ ใช้งานได้ ... setter ตัวนี้เลยใช้ chaining ครับ
/**
* This method is DEPRECATED, Please use setProperty instead.
*/
public void setDefaultProperty( String key, String value ) {
setProperty( key, value );
}
แล้วก็เอาไปใช้ chaining อย่างอื่นด้วย
#11
Posted 02 March 2004 - 08:06 AM
ดังนั้นการเข้าถึง Object ด้วย method ทำให้เราแน่ใจได้ว่า Object อยู่ในสถานะที่ถูกต้องเสมอ
#12
Posted 02 March 2004 - 01:08 PM
interface เป็นการกำหนดข้อตกลงระหว่างตัว class กับผู้ใช้ class ซึ่งอาจเป็น class อื่น ว่าผู้ใช้จะสามารถเรียกใช้ service อะไรจาก instance ของ class นั้นๆได้บ้าง
Interface เป็นการกำหนดข้อตกลงก็จริง แต่ยังห่างไกลจากคำว่า contract ที่ใช้ใน design by contract อยู่มากโข ซึ่งจะหาโอกาสพูดถึงต่อไป แต่หากใครสนใจ แนะนำให้ศึกษาภาษา Eiffel เพิ่มเติมครับ เพราะภาษานี้ถือได้ว่าเป็นที่สุดของภาษาทางด้าน Object oriented และ software engineering เลยทีเดียว
implementation เป็นการทำงานจริงที่เกิดขึ้นหลังฉาก โดยผู้ใช้ไม่ต้องสนใจว่าจะทำงานอย่างไร สนใจแค่ว่าเรียกใช้ยังไงและได้ผลลัพท์อะไรเท่านั้นเอง
การแยก interface กับ implementation ออกจากกันนี้ ทำให้ผู้ใช้สนใจแค่ interface ไม่ต้องสนใจ implemenation จึงถูกเรียกบ่อยๆว่า information hiding เป็นประโยชน์มากสำหรับทั้ง class provider และ class user
class user เขียนโปรแกรมขึ้นครั้งเดียว โดยเรียกใช้ interface ที่ class provider จัดให้ แล้วไม่ต้องสนใจว่าต่อไปหากเกิดการเปลี่ยนแปลงแก้ไขใน class ที่ใช้แล้วจะต้องมาแก้โปรแกรมตามอีก เช่นเปลี่ยน algorithm การทำงาน เป็นต้น
class provider ก็ได้ประโยชน์จากการเป็นอิสระในการเลือกวิธีการเขียน implementation ได้โดยสะดวก หากจะแก้ไขเปลี่ยนแปลงอะไรทำได้ตามใจ ตราบเทำที่ยังยึดตาม interface ที่ตกลงกันไว้
getter/setter เป็น interface ที่ class สัญญาว่าจะจัดให้มี แต่วิธีการ get/set จริงๆใน implementation นั้น provider ขอทำเองนะ class user ไม่ต้องมาสนใจว่ามายังไง
ตัวอย่างเช่นใน getPrice() มีวิธีการimplement ได้หลายวิธี
1. return ค่าของ private double price; ก็ได้ ซึ่งใช้บ่อย และก็มักไม่รู้กันว่าทำไปทำไม
2. return ค่าที่คำนวนจาก price - discount ก็ได้
3. เรียกเอาจากที่อื่นๆ เช่น amazon.com ผ่าน web service ก็ได้
4. อื่นๆอีกมากมาย
ดังนั้นจะเห็นว่าผู้ใช้ไม่ต้องสนใน implementation ว่าทำอย่างไรเลย แล้ว provider ก็เป็นอิสระที่จะ implement
#13
Posted 02 March 2004 - 01:23 PM
#14
Posted 02 March 2004 - 04:34 PM
อย่างที่บอกนะครับว่า interface เป็นเพียงการกำหนดข้อตกลงระหว่างตัว class ที่ implement interface กับผู้ใช้ class ซึ่งอาจเป็น class อื่น ว่าผู้ใช้จะสามารถเรียกใช้ service อะไรจาก instance ของ class นั้นๆได้บ้าง
แต่ contract (พันธะสัญญา) มีมากกว่านั้น นอกเหนือจากกำหนดเรื่องว่าจะเรียกใช้อะไรได้แล้ว ยังกำหนดสัญญาที่ต้องทำตามในการขอใช้บริการจาก client และการให้บริการจาก contractor ด้วย
หาก contract ถูก violate ไม่ว่าจะในขั้นตอนใดของการขอบริการหรือการให้บริการ จะนับว่า ผลลัพท์ที่ได้ไม่อาจเชื่อถือว่าถูกต้องได้
assertion มีการใช้ใน design by contract อยู่ 4 ลักษณะดังนี้
1. precondition คือ required state ที่ต้อง meet ก่อนเริ่มทำงาน
2. postcondition คือ ensured state ที่ต้องได้เวลาส่งผลลัพท์กลับ
3. invariant คือ state ที่ห้ามเกิดการเปลี่ยนแปลง
4. variant คือ state ที่กำหนดให้ต้องเปลี่ยนแปลง
assertion ทั้งหมดนี้จะเป็นตัวกำหนด context ที่ client และ contract จะทำงานร่วมกัน
หาก client เรียกขอใช้บริการจาก contractor โดยรักษา contract ให้ได้ตาม precondition และ class invariant แล้ว, ก่อนส่งผลลัพท์กลับคืน contract ก็สัญญาที่จะรักษา contract ให้ตรงตาม postcondition และ class invariant
ผลที่เกิดขึ้นจากการมี contract คือได้ผลลัพท์ที่ถูกต้องแน่นอนและสุดท้ายก็จะได้งานที่มีคุณภาพสูง
ภาษา java นั้นไม่มีการรองรับ design by contract โดยตรง แต่เนื่องจากเป็นที่ยอมรับกันทั่วไปในวงการ software engineering ว่า design by contract ช่วยส่งเสริมให้เกิดงานคุณภาพดี จึงมีความพยายามที่จะผนวก design by contract เข้าไปใช้ในภาษา java เช่นกลุ่มคนเหล่านี้
http://www.reliable-...t/iContract.htm
http://jcontractor.sourceforge.net/
http://www.publicstaticvoidmain.com/
http://semantik.info...nburg.de/~jass/
#15
Posted 03 March 2004 - 12:44 AM
0 user(s) are reading this topic
0 members, 0 guests, 0 anonymous users












