You have four separate conditions on some string $pw. To test these, it makes the most sense to test them one after the other. It makes the most sense as the tests are easier to understand, and we're free to modify the restrictions separate from each other.
if [[ $pw == [[:upper:]]* ]] &&
[[ $pw == *[[:lower:]]* ]] &&
[[ $pw == *[[:digit:]]* ]] &&
[ "${#pw}" -ge 8 ] && [ "${#pw}" -lt 16 ]
then
echo valid
else
echo invalid
fi
The code above doesn't actually use regular expressions as it's not needed, and it assumes that "number" means "digit" as opposed to "any number in any notation".
Note that it's not just difficult to do the length test as part of a single regular expression, it is also unreasonably difficult to, at the same time, make sure that the string contains at least one lower-case character and a digit, in any order. You may possibly do this in one expression, but it would be awkward.